securemark 0.281.4 → 0.283.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/CHANGELOG.md +9 -0
- package/design.md +5 -9
- package/dist/index.js +7354 -7166
- package/package.json +2 -2
- package/src/combinator/control/manipulation/convert.ts +4 -8
- package/src/combinator/control/manipulation/indent.ts +3 -5
- package/src/combinator/control/manipulation/scope.ts +6 -3
- package/src/combinator/control/manipulation/surround.ts +17 -2
- package/src/combinator/data/parser/context.test.ts +6 -6
- package/src/combinator/data/parser/context.ts +25 -39
- package/src/combinator/data/parser.ts +2 -3
- package/src/parser/api/bind.ts +6 -6
- package/src/parser/api/parse.test.ts +17 -16
- package/src/parser/api/parse.ts +0 -3
- package/src/parser/block/blockquote.ts +4 -3
- package/src/parser/block/dlist.test.ts +1 -1
- package/src/parser/block/dlist.ts +6 -6
- package/src/parser/block/extension/aside.ts +4 -3
- package/src/parser/block/extension/example.ts +4 -3
- package/src/parser/block/extension/table.ts +7 -7
- package/src/parser/block/heading.ts +3 -2
- package/src/parser/block/ilist.ts +2 -1
- package/src/parser/block/olist.ts +4 -3
- package/src/parser/block/reply/cite.ts +3 -3
- package/src/parser/block/reply/quote.ts +3 -3
- package/src/parser/block/sidefence.ts +2 -1
- package/src/parser/block/table.ts +9 -9
- package/src/parser/block/ulist.ts +6 -5
- package/src/parser/block.ts +16 -5
- package/src/parser/context.ts +9 -21
- package/src/parser/inline/annotation.ts +5 -4
- package/src/parser/inline/autolink/email.ts +2 -1
- package/src/parser/inline/autolink/url.ts +6 -5
- package/src/parser/inline/autolink.ts +2 -2
- package/src/parser/inline/bracket.ts +17 -17
- package/src/parser/inline/code.test.ts +1 -1
- package/src/parser/inline/code.ts +2 -1
- package/src/parser/inline/deletion.ts +3 -3
- package/src/parser/inline/emphasis.ts +3 -3
- package/src/parser/inline/emstrong.ts +3 -3
- package/src/parser/inline/extension/index.test.ts +4 -4
- package/src/parser/inline/extension/index.ts +18 -6
- package/src/parser/inline/extension/indexee.ts +37 -7
- package/src/parser/inline/extension/indexer.test.ts +2 -2
- package/src/parser/inline/extension/indexer.ts +2 -1
- package/src/parser/inline/extension/label.ts +2 -2
- package/src/parser/inline/extension/placeholder.ts +4 -4
- package/src/parser/inline/html.ts +2 -2
- package/src/parser/inline/htmlentity.ts +2 -1
- package/src/parser/inline/insertion.ts +3 -3
- package/src/parser/inline/link.test.ts +2 -2
- package/src/parser/inline/link.ts +7 -7
- package/src/parser/inline/mark.test.ts +2 -2
- package/src/parser/inline/mark.ts +3 -3
- package/src/parser/inline/math.test.ts +3 -3
- package/src/parser/inline/math.ts +6 -5
- package/src/parser/inline/media.test.ts +1 -1
- package/src/parser/inline/media.ts +5 -5
- package/src/parser/inline/reference.ts +6 -5
- package/src/parser/inline/remark.ts +2 -2
- package/src/parser/inline/ruby.ts +3 -3
- package/src/parser/inline/strong.ts +3 -3
- package/src/parser/inline/template.ts +4 -4
- package/src/parser/inline.ts +1 -0
- package/src/parser/source/escapable.ts +2 -1
- package/src/parser/source/str.ts +3 -2
- package/src/parser/source/text.ts +2 -1
- package/src/parser/source/unescapable.ts +2 -1
- package/src/combinator/data/parser/context/memo.ts +0 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securemark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.283.0",
|
|
4
4
|
"description": "Secure markdown renderer working on browsers for user input data.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/falsandtru/securemark",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"spica": "0.0.
|
|
31
|
+
"spica": "0.0.805"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/dompurify": "3.0.5",
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Parser, Ctx, Context, check } from '../../data/parser';
|
|
2
|
-
import { max } from 'spica/alias';
|
|
3
2
|
|
|
4
|
-
// 設計上キャッシュが汚染されるが運用で回避可能
|
|
5
|
-
// 変換の前または後のみキャッシュされるなら問題ない
|
|
6
3
|
export function convert<P extends Parser<unknown>>(conv: (source: string, context: Context<P>) => string, parser: P, empty?: boolean): P;
|
|
7
4
|
export function convert<T>(conv: (source: string, context: Ctx) => string, parser: Parser<T>, empty = false): Parser<T> {
|
|
8
5
|
assert(parser);
|
|
@@ -10,13 +7,12 @@ export function convert<T>(conv: (source: string, context: Ctx) => string, parse
|
|
|
10
7
|
if (source === '') return;
|
|
11
8
|
const src = conv(source, context);
|
|
12
9
|
if (src === '') return empty ? [[], ''] : undefined;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
context.
|
|
16
|
-
context.offset += offset;
|
|
10
|
+
const sub = source.endsWith(src);
|
|
11
|
+
const { log } = context;
|
|
12
|
+
context.log = sub ? log : {};
|
|
17
13
|
const result = parser({ source: src, context });
|
|
18
14
|
assert(check(src, result));
|
|
19
|
-
context.
|
|
15
|
+
context.log = log;
|
|
20
16
|
return result;
|
|
21
17
|
};
|
|
22
18
|
}
|
|
@@ -20,12 +20,10 @@ export function indent<T>(opener: RegExp | Parser<T>, parser?: Parser<T> | boole
|
|
|
20
20
|
([indent]) => indent.length * 2 + +(indent[0] === ' '), {})), separation),
|
|
21
21
|
(lines, rest, context) => {
|
|
22
22
|
assert(parser = parser as Parser<T>);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
context.offset ??= 0;
|
|
26
|
-
context.offset += offset;
|
|
23
|
+
const { log } = context;
|
|
24
|
+
context.log = {};
|
|
27
25
|
const result = parser({ source: trimBlockEnd(lines.join('')), context });
|
|
28
|
-
context.
|
|
26
|
+
context.log = log;
|
|
29
27
|
return result && exec(result) === ''
|
|
30
28
|
? [eval(result), rest]
|
|
31
29
|
: undefined;
|
|
@@ -34,11 +34,14 @@ export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T>
|
|
|
34
34
|
assert(parser);
|
|
35
35
|
return ({ source, context }) => {
|
|
36
36
|
if (source === '') return;
|
|
37
|
-
const
|
|
38
|
-
context.
|
|
37
|
+
const { log } = context;
|
|
38
|
+
context.log = {};
|
|
39
|
+
//const { resources = { clock: 0 } } = context;
|
|
40
|
+
//const clock = resources.clock;
|
|
39
41
|
const res1 = scope({ source, context });
|
|
40
42
|
assert(check(source, res1));
|
|
41
|
-
|
|
43
|
+
//resources.clock = clock;
|
|
44
|
+
context.log = log;
|
|
42
45
|
if (res1 === undefined || exec(res1).length >= source.length) return;
|
|
43
46
|
const src = source.slice(0, source.length - exec(res1).length);
|
|
44
47
|
assert(src !== '');
|
|
@@ -6,6 +6,7 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
6
6
|
opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>, optional?: false,
|
|
7
7
|
f?: (rss: [S[], SubTree<P>[], S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
8
8
|
g?: (rss: [S[], SubTree<P>[], string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
9
|
+
log?: 0 | 1 | 2 | 3,
|
|
9
10
|
): P;
|
|
10
11
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
11
12
|
opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>, optional?: boolean,
|
|
@@ -16,26 +17,29 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
16
17
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>, optional?: false,
|
|
17
18
|
f?: (rss: [S[], Tree<P>[], S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
18
19
|
g?: (rss: [S[], Tree<P>[], string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
20
|
+
log?: 0 | 1 | 2 | 3,
|
|
19
21
|
): P;
|
|
20
22
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
21
23
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>, optional?: boolean,
|
|
22
24
|
f?: (rss: [S[], Tree<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
23
25
|
g?: (rss: [S[], Tree<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
26
|
+
log?: 0 | 1 | 2 | 3,
|
|
24
27
|
): P;
|
|
25
28
|
export function surround<T>(
|
|
26
29
|
opener: string | RegExp | Parser<T>, parser: Parser<T>, closer: string | RegExp | Parser<T>, optional: boolean = false,
|
|
27
30
|
f?: (rss: [T[], T[], T[]], rest: string, context: Ctx) => Result<T>,
|
|
28
31
|
g?: (rss: [T[], T[], string], rest: string, context: Ctx) => Result<T>,
|
|
32
|
+
log: 0 | 1 | 2 | 3 = 0,
|
|
29
33
|
): Parser<T> {
|
|
30
34
|
switch (typeof opener) {
|
|
31
35
|
case 'string':
|
|
32
36
|
case 'object':
|
|
33
|
-
|
|
37
|
+
opener = match(opener);
|
|
34
38
|
}
|
|
35
39
|
switch (typeof closer) {
|
|
36
40
|
case 'string':
|
|
37
41
|
case 'object':
|
|
38
|
-
|
|
42
|
+
closer = match(closer);
|
|
39
43
|
}
|
|
40
44
|
return ({ source, context }) => {
|
|
41
45
|
const lmr_ = source;
|
|
@@ -45,6 +49,13 @@ export function surround<T>(
|
|
|
45
49
|
if (res1 === undefined) return;
|
|
46
50
|
const rl = eval(res1);
|
|
47
51
|
const mr_ = exec(res1);
|
|
52
|
+
if (log & 1) {
|
|
53
|
+
const { log = {}, offset = 0 } = context;
|
|
54
|
+
for (let i = 0; i < source.length - mr_.length; ++i) {
|
|
55
|
+
if (source[i] !== source[0]) break;
|
|
56
|
+
if (source.length + offset - i in log) return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
48
59
|
const res2 = mr_ !== '' ? parser({ source: mr_, context }) : undefined;
|
|
49
60
|
assert(check(mr_, res2));
|
|
50
61
|
const rm = eval(res2);
|
|
@@ -55,6 +66,10 @@ export function surround<T>(
|
|
|
55
66
|
const rr = eval(res3);
|
|
56
67
|
const rest = exec(res3, r_);
|
|
57
68
|
if (rest.length === lmr_.length) return;
|
|
69
|
+
if (log & 2 && rr === undefined) {
|
|
70
|
+
const { log = {}, offset = 0 } = context;
|
|
71
|
+
log[source.length + offset] = 0;
|
|
72
|
+
}
|
|
58
73
|
return rr
|
|
59
74
|
? f
|
|
60
75
|
? f([rl, rm!, rr], rest, context)
|
|
@@ -9,11 +9,11 @@ describe('Unit: combinator/data/parser/context', () => {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
describe('reset', () => {
|
|
12
|
-
const parser: Parser<number> = some(creation(
|
|
12
|
+
const parser: Parser<number> = some(creation(1, 1,
|
|
13
13
|
({ source, context }) => [[context.resources?.clock ?? NaN], source.slice(1)]));
|
|
14
14
|
|
|
15
15
|
it('root', () => {
|
|
16
|
-
const base: Context = { resources: { clock: 3,
|
|
16
|
+
const base: Context = { resources: { clock: 3, recursions: [1] } };
|
|
17
17
|
const ctx: Context = {};
|
|
18
18
|
assert.deepStrictEqual(reset(base, parser)({ source: '123', context: ctx }), [[3, 2, 1], '']);
|
|
19
19
|
assert(base.resources?.clock === 3);
|
|
@@ -24,8 +24,8 @@ describe('Unit: combinator/data/parser/context', () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('node', () => {
|
|
27
|
-
const base: Context = { resources: { clock: 3,
|
|
28
|
-
const ctx: Context = { resources: { clock: 1,
|
|
27
|
+
const base: Context = { resources: { clock: 3, recursions: [1] } };
|
|
28
|
+
const ctx: Context = { resources: { clock: 1, recursions: [1] } };
|
|
29
29
|
assert.deepStrictEqual(reset(base, parser)({ source: '1', context: ctx }), [[1], '']);
|
|
30
30
|
assert(base.resources?.clock === 3);
|
|
31
31
|
assert(ctx.resources?.clock === 0);
|
|
@@ -36,12 +36,12 @@ describe('Unit: combinator/data/parser/context', () => {
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
describe('context', () => {
|
|
39
|
-
const parser: Parser<boolean, Context> = some(creation(
|
|
39
|
+
const parser: Parser<boolean, Context> = some(creation(1, 1,
|
|
40
40
|
({ source, context }) => [[context.status!], source.slice(1)]));
|
|
41
41
|
|
|
42
42
|
it('', () => {
|
|
43
43
|
const base: Context = { status: true };
|
|
44
|
-
const ctx: Context = { resources: { clock: 3,
|
|
44
|
+
const ctx: Context = { resources: { clock: 3, recursions: [1] } };
|
|
45
45
|
assert.deepStrictEqual(context(base, parser)({ source: '123', context: ctx }), [[true, true, true], '']);
|
|
46
46
|
assert(ctx.resources?.clock === 0);
|
|
47
47
|
assert(ctx.status === undefined);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ObjectCreate } from 'spica/alias';
|
|
2
|
-
import { Parser, Result, Ctx, Tree, Context
|
|
3
|
-
import {
|
|
1
|
+
import { ObjectCreate, min } from 'spica/alias';
|
|
2
|
+
import { Parser, Result, Ctx, Tree, Context } from '../../data/parser';
|
|
3
|
+
import { clone } from 'spica/assign';
|
|
4
4
|
|
|
5
5
|
export function reset<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
6
6
|
export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
@@ -24,7 +24,9 @@ export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
|
24
24
|
|
|
25
25
|
function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: readonly [string, unknown][], values: unknown[], reset?: boolean): Result<Tree<P>>;
|
|
26
26
|
function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: readonly [string, unknown][], values: unknown[], reset = false): Result<T> {
|
|
27
|
-
|
|
27
|
+
if (reset) {
|
|
28
|
+
context.log = {};
|
|
29
|
+
}
|
|
28
30
|
for (let i = 0; i < changes.length; ++i) {
|
|
29
31
|
const change = changes[i];
|
|
30
32
|
const prop = change[0];
|
|
@@ -35,7 +37,7 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: read
|
|
|
35
37
|
assert(!context.precedence);
|
|
36
38
|
assert(!context.delimiters);
|
|
37
39
|
assert(!context.state);
|
|
38
|
-
context[prop as string] ??=
|
|
40
|
+
context[prop as string] ??= clone({}, change[1] as object);
|
|
39
41
|
continue;
|
|
40
42
|
}
|
|
41
43
|
values[i] = context[prop];
|
|
@@ -56,51 +58,35 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: read
|
|
|
56
58
|
return result;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
export function syntax<P extends Parser<unknown>>(
|
|
60
|
-
export function syntax<T>(
|
|
61
|
+
export function syntax<P extends Parser<unknown>>(precedence: number, state: number, parser: P): P;
|
|
62
|
+
export function syntax<T>(prec: number, state: number, parser: Parser<T>): Parser<T> {
|
|
61
63
|
return precedence(prec, ({ source, context }) => {
|
|
62
64
|
if (source === '') return;
|
|
63
|
-
const memo = context.memo ??= new Memo();
|
|
64
65
|
context.offset ??= 0;
|
|
65
|
-
const position = source.length + context.offset!;
|
|
66
66
|
const stateOuter = context.state ?? 0;
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const result: Result<T> = cache
|
|
70
|
-
? cache.length === 0
|
|
71
|
-
? undefined
|
|
72
|
-
: [cache[0] as T[], source.slice(cache[1])]
|
|
73
|
-
: parser({ source, context });
|
|
74
|
-
if (stateOuter && !cache && syntax & memo.targets) {
|
|
75
|
-
memo.set(position, syntax, stateInner, eval(result), source.length - exec(result, '').length);
|
|
76
|
-
}
|
|
77
|
-
else if (!stateOuter && result && memo.length >= position + memo.margin) {
|
|
78
|
-
memo.resize(position + memo.margin);
|
|
79
|
-
}
|
|
67
|
+
context.state = stateOuter | state;
|
|
68
|
+
const result: Result<T> = parser({ source, context });
|
|
80
69
|
context.state = stateOuter;
|
|
81
70
|
return result;
|
|
82
71
|
});
|
|
83
72
|
}
|
|
84
73
|
|
|
85
|
-
export function creation<P extends Parser<unknown>>(parser: P): P;
|
|
86
|
-
export function creation
|
|
87
|
-
export function creation<P extends Parser<unknown>>(cost: number, recursion: boolean, parser: P): P;
|
|
88
|
-
export function creation(cost: number | Parser<unknown>, recursion?: boolean | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
89
|
-
if (typeof cost === 'function') return creation(1, true, cost);
|
|
90
|
-
if (typeof recursion === 'function') return creation(cost, true, recursion);
|
|
74
|
+
export function creation<P extends Parser<unknown>>(cost: number, recursion: number, parser: P): P;
|
|
75
|
+
export function creation(cost: number, recursion: number, parser: Parser<unknown>): Parser<unknown> {
|
|
91
76
|
assert(cost >= 0);
|
|
92
|
-
assert(recursion
|
|
77
|
+
assert(recursion >= 0);
|
|
93
78
|
return ({ source, context }) => {
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
79
|
+
const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
|
|
80
|
+
const { recursions } = resources;
|
|
81
|
+
assert(recursions.length > 0);
|
|
82
|
+
const rec = min(recursion, recursions.length);
|
|
83
|
+
if (rec > 0 && recursions[rec - 1] < 1) throw new Error('Too much recursion');
|
|
84
|
+
rec > 0 && --recursions[rec - 1];
|
|
85
|
+
const result = parser({ source, context });
|
|
86
|
+
rec > 0 && ++recursions[rec - 1];
|
|
87
|
+
if (result === undefined) return;
|
|
88
|
+
if (resources.clock < cost) throw new Error('Too many creations');
|
|
89
|
+
resources.clock -= cost;
|
|
104
90
|
return result;
|
|
105
91
|
};
|
|
106
92
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Delimiters } from './parser/context/delimiter';
|
|
2
|
-
import { Memo } from './parser/context/memo';
|
|
3
2
|
|
|
4
3
|
export type Parser<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
5
4
|
= (input: Input<C>) => Result<T, C, D>;
|
|
@@ -14,13 +13,13 @@ export type Result<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
|
14
13
|
export interface Ctx {
|
|
15
14
|
readonly resources?: {
|
|
16
15
|
clock: number;
|
|
17
|
-
|
|
16
|
+
recursions: number[];
|
|
18
17
|
};
|
|
19
18
|
offset?: number;
|
|
20
19
|
precedence?: number;
|
|
21
20
|
delimiters?: Delimiters;
|
|
22
21
|
state?: number;
|
|
23
|
-
|
|
22
|
+
log?: Record<number, 0>;
|
|
24
23
|
}
|
|
25
24
|
export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
|
|
26
25
|
export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown, Ctx, infer D> ? D : never;
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { ParserSettings, Progress } from '../../..';
|
|
2
2
|
import { MarkdownParser } from '../../../markdown';
|
|
3
3
|
import { eval } from '../../combinator/data/parser';
|
|
4
|
-
import { Memo } from '../../combinator/data/parser/context/memo';
|
|
5
|
-
import { Syntax, Margin } from '../context';
|
|
6
4
|
import { segment, validate, MAX_INPUT_SIZE } from '../segment';
|
|
7
5
|
import { header } from '../header';
|
|
8
6
|
import { block } from '../block';
|
|
@@ -24,7 +22,6 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
24
22
|
let context: MarkdownParser.Context = {
|
|
25
23
|
...settings,
|
|
26
24
|
host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
27
|
-
memo: new Memo(Syntax.targets, Margin),
|
|
28
25
|
};
|
|
29
26
|
assert(!context.offset);
|
|
30
27
|
assert(!context.precedence);
|
|
@@ -87,9 +84,10 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
87
84
|
// All deletion processes always run after all addition processes have done.
|
|
88
85
|
// Therefore any `base` node will never be unavailable by deletions until all the dependent `el` nodes are added.
|
|
89
86
|
push(adds, es.map(el => [el, base] as const));
|
|
87
|
+
adds.reverse();
|
|
90
88
|
while (adds.length > 0) {
|
|
91
89
|
assert(rev === revision);
|
|
92
|
-
const [el, base] = adds.
|
|
90
|
+
const [el, base] = adds.pop()!;
|
|
93
91
|
target.insertBefore(el, base);
|
|
94
92
|
assert(el.parentNode);
|
|
95
93
|
yield { type: 'block', value: el };
|
|
@@ -103,17 +101,19 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
103
101
|
push(dels, es.map(el => [el]));
|
|
104
102
|
}
|
|
105
103
|
assert(blocks.length === sourceSegments.length);
|
|
104
|
+
adds.reverse();
|
|
106
105
|
while (adds.length > 0) {
|
|
107
106
|
assert(rev === revision);
|
|
108
|
-
const [el, base] = adds.
|
|
107
|
+
const [el, base] = adds.pop()!;
|
|
109
108
|
target.insertBefore(el, base);
|
|
110
109
|
assert(el.parentNode);
|
|
111
110
|
yield { type: 'block', value: el };
|
|
112
111
|
if (rev !== revision) return yield { type: 'cancel' };
|
|
113
112
|
}
|
|
113
|
+
dels.reverse();
|
|
114
114
|
while (dels.length > 0) {
|
|
115
115
|
assert(rev === revision);
|
|
116
|
-
const [el] = dels.
|
|
116
|
+
const [el] = dels.pop()!;
|
|
117
117
|
el.parentNode?.removeChild(el);
|
|
118
118
|
assert(!el.parentNode);
|
|
119
119
|
yield { type: 'block', value: el };
|
|
@@ -298,35 +298,35 @@ describe('Unit: parser/api/parse', () => {
|
|
|
298
298
|
|
|
299
299
|
it('recursion', () => {
|
|
300
300
|
assert.deepStrictEqual(
|
|
301
|
-
[...parse('{'.repeat(
|
|
302
|
-
[`<p>${'{'.repeat(
|
|
301
|
+
[...parse(`${'{'.repeat(20)}a`).children].map(el => el.outerHTML),
|
|
302
|
+
[`<p>${'{'.repeat(20)}a</p>`]);
|
|
303
303
|
assert.deepStrictEqual(
|
|
304
|
-
[...parse('{'.repeat(
|
|
304
|
+
[...parse(`${'{'.repeat(21)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
305
305
|
[
|
|
306
306
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
307
|
-
`<pre class="error" translate="no">${'{'.repeat(
|
|
307
|
+
`<pre class="error" translate="no">${'{'.repeat(21)}a</pre>`,
|
|
308
308
|
]);
|
|
309
309
|
assert.deepStrictEqual(
|
|
310
|
-
[...parse('('.repeat(
|
|
311
|
-
[`<p>${'('.repeat(
|
|
310
|
+
[...parse(`${'('.repeat(22)}a`).children].map(el => el.outerHTML),
|
|
311
|
+
[`<p>${'('.repeat(22)}a</p>`]);
|
|
312
312
|
assert.deepStrictEqual(
|
|
313
|
-
[...parse('('.repeat(
|
|
313
|
+
[...parse(`${'('.repeat(23)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
314
314
|
[
|
|
315
315
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
316
|
-
`<pre class="error" translate="no">${'('.repeat(
|
|
316
|
+
`<pre class="error" translate="no">${'('.repeat(23)}a</pre>`,
|
|
317
317
|
]);
|
|
318
318
|
assert.deepStrictEqual(
|
|
319
|
-
[...parse('['.repeat(
|
|
320
|
-
[`<p>${'['.repeat(
|
|
319
|
+
[...parse(`${'['.repeat(23)}a`).children].map(el => el.outerHTML),
|
|
320
|
+
[`<p>${'['.repeat(23)}a</p>`]);
|
|
321
321
|
assert.deepStrictEqual(
|
|
322
|
-
[...parse('['.repeat(
|
|
322
|
+
[...parse(`${'['.repeat(24)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
323
323
|
[
|
|
324
324
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
325
|
-
`<pre class="error" translate="no">${'['.repeat(
|
|
325
|
+
`<pre class="error" translate="no">${'['.repeat(24)}a</pre>`,
|
|
326
326
|
]);
|
|
327
327
|
assert.deepStrictEqual(
|
|
328
|
-
[...parse('['.repeat(
|
|
329
|
-
[`<p>${'['.repeat(
|
|
328
|
+
[...parse(`${'['.repeat(22)}\na`).children].map(el => el.outerHTML),
|
|
329
|
+
[`<p>${'['.repeat(22)}<br>a</p>`]);
|
|
330
330
|
});
|
|
331
331
|
|
|
332
332
|
if (!navigator.userAgent.includes('Chrome')) return;
|
|
@@ -351,9 +351,10 @@ describe('Unit: parser/api/parse', () => {
|
|
|
351
351
|
it('recovery', function () {
|
|
352
352
|
this.timeout(5000);
|
|
353
353
|
assert.deepStrictEqual(
|
|
354
|
-
[...parse(
|
|
354
|
+
[...parse(`${'{'.repeat(21)}\n\na`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
355
355
|
[
|
|
356
|
-
`<
|
|
356
|
+
`<h1 id="error:rnd" class="error">Error: Too much recursion</h1>`,
|
|
357
|
+
`<pre class="error" translate="no">${'{'.repeat(21)}\n</pre>`,
|
|
357
358
|
'<p>a</p>',
|
|
358
359
|
]);
|
|
359
360
|
});
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { ParserOptions } from '../../..';
|
|
2
2
|
import { MarkdownParser } from '../../../markdown';
|
|
3
3
|
import { eval } from '../../combinator/data/parser';
|
|
4
|
-
import { Memo } from '../../combinator/data/parser/context/memo';
|
|
5
|
-
import { Syntax, Margin } from '../context';
|
|
6
4
|
import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
|
|
7
5
|
import { header } from '../header';
|
|
8
6
|
import { block } from '../block';
|
|
@@ -27,7 +25,6 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
27
25
|
id: opts.id ?? context?.id,
|
|
28
26
|
caches: context?.caches,
|
|
29
27
|
resources: context?.resources,
|
|
30
|
-
memo: new Memo(Syntax.targets, Margin),
|
|
31
28
|
};
|
|
32
29
|
assert(!context.offset);
|
|
33
30
|
assert(!context.precedence);
|
|
@@ -2,6 +2,7 @@ import { BlockquoteParser } from '../block';
|
|
|
2
2
|
import { union, some, creation, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
|
|
3
3
|
import { autolink } from '../autolink';
|
|
4
4
|
import { contentline } from '../source';
|
|
5
|
+
import { Recursion } from '../context';
|
|
5
6
|
import { parse } from '../api/parse';
|
|
6
7
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
8
|
|
|
@@ -19,7 +20,7 @@ const indent = block(open(opener, some(contentline, /^>(?:$|\s)/)), false);
|
|
|
19
20
|
const unindent = (source: string) => source.replace(/(?<=^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '');
|
|
20
21
|
|
|
21
22
|
const source: BlockquoteParser.SourceParser = lazy(() => fmap(
|
|
22
|
-
some(creation(
|
|
23
|
+
some(creation(0, Recursion.blockquote, union([
|
|
23
24
|
rewrite(
|
|
24
25
|
indent,
|
|
25
26
|
convert(unindent, source, true)),
|
|
@@ -30,11 +31,11 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
|
|
|
30
31
|
ns => [html('blockquote', ns)]));
|
|
31
32
|
|
|
32
33
|
const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
|
|
33
|
-
some(creation(
|
|
34
|
+
some(creation(0, Recursion.blockquote, union([
|
|
34
35
|
rewrite(
|
|
35
36
|
indent,
|
|
36
37
|
convert(unindent, markdown, true)),
|
|
37
|
-
creation(
|
|
38
|
+
creation(10, Recursion.ignore,
|
|
38
39
|
rewrite(
|
|
39
40
|
some(contentline, opener),
|
|
40
41
|
convert(unindent, ({ source, context }) => {
|
|
@@ -64,7 +64,7 @@ describe('Unit: parser/block/dlist', () => {
|
|
|
64
64
|
assert.deepStrictEqual(inspect(parser('~ a\n: b\n~ c\n: d\n~ e\n: f')), [['<dl><dt id="index::a">a</dt><dd>b</dd><dt id="index::c">c</dt><dd>d</dd><dt id="index::e">e</dt><dd>f</dd></dl>'], '']);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
it('
|
|
67
|
+
it('indexer', () => {
|
|
68
68
|
assert.deepStrictEqual(inspect(parser('~ a [|b]')), [['<dl><dt id="index::b">a<span class="indexer" data-index="b"></span></dt><dd></dd></dl>'], '']);
|
|
69
69
|
assert.deepStrictEqual(inspect(parser('~ a [|b]\\')), [['<dl><dt id="index::a_b">a <span class="invalid">b</span></dt><dd></dd></dl>'], '']);
|
|
70
70
|
assert.deepStrictEqual(inspect(parser('~ A')), [['<dl><dt id="index::A">A</dt><dd></dd></dl>'], '']);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DListParser } from '../block';
|
|
2
|
-
import { union, inits, some,
|
|
3
|
-
import { inline, indexee, indexer } from '../inline';
|
|
2
|
+
import { union, inits, some, state, block, line, validate, rewrite, open, lazy, fmap } from '../../combinator';
|
|
3
|
+
import { inline, indexee, indexer, dataindex } from '../inline';
|
|
4
4
|
import { anyline } from '../source';
|
|
5
5
|
import { State } from '../context';
|
|
6
6
|
import { lineable } from '../util';
|
|
@@ -17,20 +17,20 @@ export const dlist: DListParser = lazy(() => block(fmap(validate(
|
|
|
17
17
|
]))),
|
|
18
18
|
es => [html('dl', fillTrailingDescription(es))])));
|
|
19
19
|
|
|
20
|
-
const term: DListParser.TermParser =
|
|
20
|
+
const term: DListParser.TermParser = line(indexee(fmap(open(
|
|
21
21
|
/^~[^\S\n]+(?=\S)/,
|
|
22
22
|
visualize(trimBlankStart(some(union([indexer, inline])))),
|
|
23
23
|
true),
|
|
24
|
-
ns => [html('dt', trimNodeEnd(defrag(ns)))])))
|
|
24
|
+
ns => [html('dt', { 'data-index': dataindex(ns) }, trimNodeEnd(defrag(ns)))])));
|
|
25
25
|
|
|
26
|
-
const desc: DListParser.DescriptionParser =
|
|
26
|
+
const desc: DListParser.DescriptionParser = block(fmap(open(
|
|
27
27
|
/^:[^\S\n]+(?=\S)|/,
|
|
28
28
|
rewrite(
|
|
29
29
|
some(anyline, /^[~:][^\S\n]+\S/),
|
|
30
30
|
visualize(lineable(some(union([inline]))))),
|
|
31
31
|
true),
|
|
32
32
|
ns => [html('dd', trimNodeEnd(defrag(ns)))]),
|
|
33
|
-
false)
|
|
33
|
+
false);
|
|
34
34
|
|
|
35
35
|
function fillTrailingDescription(es: HTMLElement[]): HTMLElement[] {
|
|
36
36
|
return es.length > 0 && es[es.length - 1].tagName === 'DT'
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ExtensionParser } from '../../block';
|
|
2
|
-
import { block, validate, fence, fmap } from '../../../combinator';
|
|
2
|
+
import { creation, block, validate, fence, fmap } from '../../../combinator';
|
|
3
3
|
import { identity } from '../../inline/extension/indexee';
|
|
4
|
+
import { Recursion } from '../../context';
|
|
4
5
|
import { parse } from '../../api/parse';
|
|
5
6
|
import { html } from 'typed-dom/dom';
|
|
6
7
|
|
|
7
|
-
export const aside: ExtensionParser.AsideParser = block(validate('~~~', fmap(
|
|
8
|
+
export const aside: ExtensionParser.AsideParser = creation(0, Recursion.block, block(validate('~~~', fmap(
|
|
8
9
|
fence(/^(~{3,})aside(?!\S)([^\n]*)(?:$|\n)/, 300),
|
|
9
10
|
// Bug: Type mismatch between outer and inner.
|
|
10
11
|
([body, overflow, closer, opener, delim, param]: string[], _, context) => {
|
|
@@ -42,4 +43,4 @@ export const aside: ExtensionParser.AsideParser = block(validate('~~~', fmap(
|
|
|
42
43
|
references,
|
|
43
44
|
]),
|
|
44
45
|
];
|
|
45
|
-
})));
|
|
46
|
+
}))));
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { ExtensionParser } from '../../block';
|
|
2
2
|
import { eval } from '../../../combinator/data/parser';
|
|
3
|
-
import { block, validate, fence, fmap } from '../../../combinator';
|
|
3
|
+
import { creation, block, validate, fence, fmap } from '../../../combinator';
|
|
4
4
|
import { parse } from '../../api/parse';
|
|
5
5
|
import { mathblock } from '../mathblock';
|
|
6
|
+
import { Recursion } from '../../context';
|
|
6
7
|
import { html } from 'typed-dom/dom';
|
|
7
8
|
|
|
8
9
|
const opener = /^(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/;
|
|
9
10
|
|
|
10
|
-
export const example: ExtensionParser.ExampleParser = block(validate('~~~', fmap(
|
|
11
|
+
export const example: ExtensionParser.ExampleParser = creation(0, Recursion.block, block(validate('~~~', fmap(
|
|
11
12
|
fence(opener, 300),
|
|
12
13
|
// Bug: Type mismatch between outer and inner.
|
|
13
14
|
([body, overflow, closer, opener, delim, type = 'markdown', param]: string[], _, context) => {
|
|
@@ -58,4 +59,4 @@ export const example: ExtensionParser.ExampleParser = block(validate('~~~', fmap
|
|
|
58
59
|
}, `${opener}${body}${closer}`),
|
|
59
60
|
];
|
|
60
61
|
}
|
|
61
|
-
})));
|
|
62
|
+
}))));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { max, min, isArray } from 'spica/alias';
|
|
2
2
|
import { ExtensionParser } from '../../block';
|
|
3
3
|
import { Tree, eval } from '../../../combinator/data/parser';
|
|
4
|
-
import { union, subsequence, inits, some,
|
|
4
|
+
import { union, subsequence, inits, some, block, line, validate, fence, rewrite, surround, open, clear, convert, dup, lazy, fmap } from '../../../combinator';
|
|
5
5
|
import { inline, medialink, media, shortmedia } from '../../inline';
|
|
6
6
|
import { str, anyline, emptyline, contentline } from '../../source';
|
|
7
7
|
import { lineable } from '../../util';
|
|
@@ -78,7 +78,7 @@ const align: AlignParser = line(fmap(
|
|
|
78
78
|
|
|
79
79
|
const delimiter = /^[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|^[#:](?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/;
|
|
80
80
|
|
|
81
|
-
const head: CellParser.HeadParser =
|
|
81
|
+
const head: CellParser.HeadParser = block(fmap(open(
|
|
82
82
|
str(/^#(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/),
|
|
83
83
|
rewrite(
|
|
84
84
|
inits([
|
|
@@ -93,9 +93,9 @@ const head: CellParser.HeadParser = creation(1, false, block(fmap(open(
|
|
|
93
93
|
])),
|
|
94
94
|
true),
|
|
95
95
|
ns => [html('th', attributes(ns.shift()! as string), trimNodeEnd(defrag(ns)))]),
|
|
96
|
-
false)
|
|
96
|
+
false);
|
|
97
97
|
|
|
98
|
-
const data: CellParser.DataParser =
|
|
98
|
+
const data: CellParser.DataParser = block(fmap(open(
|
|
99
99
|
str(/^:(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/),
|
|
100
100
|
rewrite(
|
|
101
101
|
inits([
|
|
@@ -110,15 +110,15 @@ const data: CellParser.DataParser = creation(1, false, block(fmap(open(
|
|
|
110
110
|
])),
|
|
111
111
|
true),
|
|
112
112
|
ns => [html('td', attributes(ns.shift()! as string), trimNodeEnd(defrag(ns)))]),
|
|
113
|
-
false)
|
|
113
|
+
false);
|
|
114
114
|
|
|
115
|
-
const dataline: CellParser.DatalineParser =
|
|
115
|
+
const dataline: CellParser.DatalineParser = line(
|
|
116
116
|
rewrite(
|
|
117
117
|
contentline,
|
|
118
118
|
union([
|
|
119
119
|
validate(/^!+\s/, convert(source => `:${source}`, data)),
|
|
120
120
|
convert(source => `: ${source}`, data),
|
|
121
|
-
])))
|
|
121
|
+
])));
|
|
122
122
|
|
|
123
123
|
function attributes(source: string): Record<string, string | undefined> {
|
|
124
124
|
let [, rowspan = undefined, colspan = undefined, highlight = undefined, extension = undefined] =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HeadingParser } from '../block';
|
|
2
2
|
import { union, some, state, block, line, validate, focus, rewrite, open, fmap } from '../../combinator';
|
|
3
|
-
import { inline, indexee, indexer } from '../inline';
|
|
3
|
+
import { inline, indexee, indexer, dataindex } from '../inline';
|
|
4
4
|
import { str } from '../source';
|
|
5
5
|
import { State } from '../context';
|
|
6
6
|
import { visualize, trimBlankStart, trimNodeEnd } from '../visibility';
|
|
@@ -11,6 +11,7 @@ export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
|
|
|
11
11
|
some(line(({ source }) => [[source], ''])))));
|
|
12
12
|
|
|
13
13
|
export const heading: HeadingParser = block(rewrite(segment,
|
|
14
|
+
// その他の表示制御は各所のCSSで行う。
|
|
14
15
|
state(State.annotation | State.reference | State.index | State.label | State.link | State.media,
|
|
15
16
|
line(indexee(fmap(union([
|
|
16
17
|
open(
|
|
@@ -23,7 +24,7 @@ export const heading: HeadingParser = block(rewrite(segment,
|
|
|
23
24
|
]),
|
|
24
25
|
([h, ...ns]: [string, ...(HTMLElement | string)[]]) => [
|
|
25
26
|
h.length <= 6
|
|
26
|
-
? html(`h${h.length as 1}`, trimNodeEnd(defrag(ns)))
|
|
27
|
+
? html(`h${h.length as 1}`, { 'data-index': dataindex(ns) }, trimNodeEnd(defrag(ns)))
|
|
27
28
|
: html(`h6`, {
|
|
28
29
|
class: 'invalid',
|
|
29
30
|
'data-invalid-syntax': 'heading',
|