securemark 0.258.2 → 0.258.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 +12 -0
- package/dist/index.js +210 -183
- package/markdown.d.ts +8 -10
- package/package.json +1 -1
- package/src/combinator/control/constraint/block.ts +3 -1
- package/src/combinator/control/constraint/line.ts +6 -1
- package/src/combinator/control/manipulation/indent.ts +3 -0
- package/src/combinator/data/parser/context/memo.ts +7 -4
- package/src/combinator/data/parser/context.test.ts +16 -16
- package/src/combinator/data/parser/context.ts +40 -37
- package/src/combinator/data/parser/some.ts +4 -2
- package/src/combinator/data/parser.ts +2 -2
- package/src/parser/api/bind.ts +1 -1
- package/src/parser/api/parse.test.ts +4 -4
- package/src/parser/api/parse.ts +1 -1
- package/src/parser/autolink.test.ts +3 -2
- package/src/parser/autolink.ts +17 -1
- package/src/parser/block/blockquote.ts +4 -4
- package/src/parser/block/dlist.ts +3 -3
- package/src/parser/block/extension/table.ts +4 -4
- package/src/parser/block/ilist.ts +2 -2
- package/src/parser/block/olist.ts +2 -2
- package/src/parser/block/paragraph.test.ts +3 -1
- package/src/parser/block/reply/cite.ts +2 -2
- package/src/parser/block/reply/quote.ts +3 -3
- package/src/parser/block/sidefence.ts +2 -2
- package/src/parser/block/table.ts +5 -5
- package/src/parser/block/ulist.ts +4 -4
- package/src/parser/block.ts +3 -3
- package/src/parser/context.ts +1 -1
- package/src/parser/inline/annotation.ts +7 -6
- package/src/parser/inline/autolink/email.test.ts +3 -3
- package/src/parser/inline/autolink/email.ts +2 -2
- package/src/parser/inline/autolink/url.test.ts +6 -6
- package/src/parser/inline/autolink/url.ts +2 -2
- package/src/parser/inline/autolink.ts +6 -6
- package/src/parser/inline/bracket.ts +8 -8
- package/src/parser/inline/code.ts +2 -2
- package/src/parser/inline/comment.ts +2 -2
- package/src/parser/inline/deletion.ts +5 -4
- package/src/parser/inline/emphasis.ts +5 -4
- package/src/parser/inline/emstrong.ts +5 -4
- package/src/parser/inline/extension/index.ts +9 -8
- package/src/parser/inline/extension/indexer.ts +2 -2
- package/src/parser/inline/extension/label.ts +3 -3
- package/src/parser/inline/extension/placeholder.ts +5 -4
- package/src/parser/inline/html.test.ts +1 -1
- package/src/parser/inline/html.ts +4 -4
- package/src/parser/inline/htmlentity.ts +2 -2
- package/src/parser/inline/insertion.ts +5 -4
- package/src/parser/inline/link.ts +11 -9
- package/src/parser/inline/mark.ts +5 -4
- package/src/parser/inline/math.ts +3 -3
- package/src/parser/inline/media.ts +8 -7
- package/src/parser/inline/reference.ts +8 -7
- package/src/parser/inline/ruby.ts +4 -4
- package/src/parser/inline/shortmedia.ts +2 -2
- package/src/parser/inline/strong.ts +5 -4
- package/src/parser/inline/template.ts +6 -6
- package/src/parser/inline.test.ts +5 -3
- package/src/parser/source/escapable.ts +2 -2
- package/src/parser/source/str.ts +5 -5
- package/src/parser/source/text.test.ts +16 -1
- package/src/parser/source/text.ts +5 -4
- package/src/parser/source/unescapable.ts +2 -2
- package/src/parser/visibility.ts +5 -5
package/markdown.d.ts
CHANGED
|
@@ -964,7 +964,7 @@ export namespace MarkdownParser {
|
|
|
964
964
|
Inline<'html'>,
|
|
965
965
|
Parser<HTMLElement | string, Context, [
|
|
966
966
|
HTMLParser.OpenTagParser,
|
|
967
|
-
|
|
967
|
+
HTMLParser.OpenTagParser,
|
|
968
968
|
HTMLParser.TagParser,
|
|
969
969
|
HTMLParser.TagParser,
|
|
970
970
|
]> {
|
|
@@ -972,8 +972,8 @@ export namespace MarkdownParser {
|
|
|
972
972
|
export namespace HTMLParser {
|
|
973
973
|
export interface OpenTagParser extends
|
|
974
974
|
Inline<'html/opentag'>,
|
|
975
|
-
Parser<HTMLElement, Context, [
|
|
976
|
-
|
|
975
|
+
Parser<HTMLElement | string, Context, [
|
|
976
|
+
AttributeParser,
|
|
977
977
|
]> {
|
|
978
978
|
}
|
|
979
979
|
export interface TagParser extends
|
|
@@ -983,13 +983,11 @@ export namespace MarkdownParser {
|
|
|
983
983
|
InlineParser,
|
|
984
984
|
]> {
|
|
985
985
|
}
|
|
986
|
-
export
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
]> {
|
|
992
|
-
}
|
|
986
|
+
export interface AttributeParser extends
|
|
987
|
+
Inline<'html/attribute'>,
|
|
988
|
+
Parser<string, Context, [
|
|
989
|
+
SourceParser.StrParser,
|
|
990
|
+
]> {
|
|
993
991
|
}
|
|
994
992
|
}
|
|
995
993
|
export interface InsertionParser extends
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { undefined } from 'spica/global';
|
|
2
2
|
import { Parser, exec } from '../../data/parser';
|
|
3
|
+
import { Memo } from '../../data/parser/context/memo';
|
|
3
4
|
import { firstline, isEmpty } from './line';
|
|
4
5
|
|
|
5
6
|
export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
|
|
6
7
|
export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
|
|
7
8
|
assert(parser);
|
|
8
|
-
return (source, context) => {
|
|
9
|
+
return (source, context = {}) => {
|
|
9
10
|
if (source === '') return;
|
|
11
|
+
context.memo ??= new Memo();
|
|
10
12
|
const result = parser(source, context);
|
|
11
13
|
if (!result) return;
|
|
12
14
|
const rest = exec(result);
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { undefined } from 'spica/global';
|
|
2
2
|
import { Parser, eval, exec, check } from '../../data/parser';
|
|
3
|
+
import { Memo } from '../../data/parser/context/memo';
|
|
3
4
|
|
|
4
5
|
export function line<P extends Parser<unknown>>(parser: P): P;
|
|
5
6
|
export function line<T>(parser: Parser<T>): Parser<T> {
|
|
6
7
|
assert(parser);
|
|
7
|
-
return (source, context) => {
|
|
8
|
+
return (source, context = {}) => {
|
|
8
9
|
if (source === '') return;
|
|
10
|
+
context.memo ??= new Memo();
|
|
9
11
|
const line = firstline(source);
|
|
12
|
+
const memo = context.memo!;
|
|
13
|
+
memo.offset += source.length - line.length;
|
|
10
14
|
const result = parser(line, context);
|
|
11
15
|
assert(check(line, result));
|
|
16
|
+
memo.offset -= source.length - line.length;
|
|
12
17
|
if (!result) return;
|
|
13
18
|
return isEmpty(exec(result))
|
|
14
19
|
? [eval(result), source.slice(line.length)]
|
|
@@ -21,7 +21,10 @@ export function indent<T>(opener: RegExp | Parser<T>, parser?: Parser<T> | boole
|
|
|
21
21
|
([indent]) => indent.length * 2 + +(indent[0] === ' '), [])), separation),
|
|
22
22
|
(lines, rest, context) => {
|
|
23
23
|
assert(parser = parser as Parser<T>);
|
|
24
|
+
const memo = context.memo;
|
|
25
|
+
memo && (memo.offset += rest.length);
|
|
24
26
|
const result = parser(trimBlockEnd(lines.join('')), context);
|
|
27
|
+
memo && (memo.offset -= rest.length);
|
|
25
28
|
return result && exec(result) === ''
|
|
26
29
|
? [eval(result), rest]
|
|
27
30
|
: undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export class Memo {
|
|
2
|
-
private memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
|
|
2
|
+
private readonly memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
|
|
3
3
|
public get length(): number {
|
|
4
4
|
return this.memory.length;
|
|
5
5
|
}
|
|
@@ -10,7 +10,10 @@ export class Memo {
|
|
|
10
10
|
state: number,
|
|
11
11
|
): readonly [any[], number] | readonly [] | undefined {
|
|
12
12
|
//console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
|
|
13
|
-
|
|
13
|
+
const cache = this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
|
|
14
|
+
return cache?.length === 2
|
|
15
|
+
? [cache[0].slice(), cache[1]]
|
|
16
|
+
: cache;
|
|
14
17
|
}
|
|
15
18
|
public set(
|
|
16
19
|
position: number,
|
|
@@ -24,13 +27,13 @@ export class Memo {
|
|
|
24
27
|
record[`${syntax}:${state}`] = nodes
|
|
25
28
|
? [nodes.slice(), offset]
|
|
26
29
|
: [];
|
|
27
|
-
//console.log('set', position + this.offset, syntax, state);
|
|
30
|
+
//console.log('set', position + this.offset, syntax, state, record[`${syntax}:${state}`]);
|
|
28
31
|
}
|
|
29
32
|
public clear(position: number): void {
|
|
30
33
|
const memory = this.memory;
|
|
31
34
|
for (let i = position + this.offset, len = memory.length; i < len; ++i) {
|
|
32
35
|
memory.pop();
|
|
33
36
|
}
|
|
34
|
-
//console.log('clear', position);
|
|
37
|
+
//console.log('clear', position + 1);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Parser, Ctx } from '../parser';
|
|
2
2
|
import { some } from './some';
|
|
3
3
|
import { reset, context } from './context';
|
|
4
|
-
import {
|
|
4
|
+
import { creation } from './context';
|
|
5
5
|
|
|
6
6
|
describe('Unit: combinator/data/parser/context', () => {
|
|
7
7
|
interface Context extends Ctx {
|
|
@@ -9,44 +9,44 @@ describe('Unit: combinator/data/parser/context', () => {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
describe('reset', () => {
|
|
12
|
-
const parser: Parser<number> = some(
|
|
13
|
-
(s, context) => [[context.resources?.
|
|
12
|
+
const parser: Parser<number> = some(creation(
|
|
13
|
+
(s, context) => [[context.resources?.clock ?? NaN], s.slice(1)]));
|
|
14
14
|
|
|
15
15
|
it('root', () => {
|
|
16
|
-
const base: Context = { resources: {
|
|
16
|
+
const base: Context = { resources: { clock: 3, recursion: 1 } };
|
|
17
17
|
const ctx: Context = {};
|
|
18
18
|
assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
|
|
19
|
-
assert(base.resources?.
|
|
20
|
-
assert(ctx.resources?.
|
|
19
|
+
assert(base.resources?.clock === 3);
|
|
20
|
+
assert(ctx.resources?.clock === undefined);
|
|
21
21
|
assert.throws(() => reset(base, parser)('1234', ctx));
|
|
22
|
-
assert(ctx.resources?.
|
|
22
|
+
assert(ctx.resources?.clock === undefined);
|
|
23
23
|
assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('node', () => {
|
|
27
|
-
const base: Context = { resources: {
|
|
28
|
-
const ctx: Context = { resources: {
|
|
27
|
+
const base: Context = { resources: { clock: 3, recursion: 1 } };
|
|
28
|
+
const ctx: Context = { resources: { clock: 1, recursion: 1 } };
|
|
29
29
|
assert.deepStrictEqual(reset(base, parser)('1', ctx), [[1], '']);
|
|
30
|
-
assert(base.resources?.
|
|
31
|
-
assert(ctx.resources?.
|
|
30
|
+
assert(base.resources?.clock === 3);
|
|
31
|
+
assert(ctx.resources?.clock === 0);
|
|
32
32
|
assert.throws(() => reset(base, parser)('1', ctx));
|
|
33
|
-
assert(ctx.resources?.
|
|
33
|
+
assert(ctx.resources?.clock === 0);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
describe('context', () => {
|
|
39
|
-
const parser: Parser<boolean, Context> = some(
|
|
39
|
+
const parser: Parser<boolean, Context> = some(creation(
|
|
40
40
|
(s, context) => [[context.status!], s.slice(1)]));
|
|
41
41
|
|
|
42
42
|
it('', () => {
|
|
43
43
|
const base: Context = { status: true };
|
|
44
|
-
const ctx: Context = { resources: {
|
|
44
|
+
const ctx: Context = { resources: { clock: 3, recursion: 1 } };
|
|
45
45
|
assert.deepStrictEqual(context(base, parser)('123', ctx), [[true, true, true], '']);
|
|
46
|
-
assert(ctx.resources?.
|
|
46
|
+
assert(ctx.resources?.clock === 0);
|
|
47
47
|
assert(ctx.status === undefined);
|
|
48
48
|
assert.throws(() => reset(base, parser)('1', ctx));
|
|
49
|
-
assert(ctx.resources?.
|
|
49
|
+
assert(ctx.resources?.clock === 0);
|
|
50
50
|
assert(ctx.status === undefined);
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -56,65 +56,49 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
|
|
|
56
56
|
return result;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, parser: P): P;
|
|
60
59
|
export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, parser: P): P;
|
|
61
|
-
export function syntax<T>(syntax: number, precedence: number, cost: number
|
|
62
|
-
|
|
63
|
-
parser = cost;
|
|
64
|
-
cost = 1;
|
|
65
|
-
}
|
|
66
|
-
return (source, context) => {
|
|
60
|
+
export function syntax<T>(syntax: number, precedence: number, cost: number, parser?: Parser<T>): Parser<T> {
|
|
61
|
+
return creation(cost, (source, context) => {
|
|
67
62
|
if (source === '') return;
|
|
68
|
-
context.
|
|
69
|
-
|
|
63
|
+
const memo = context.memo ??= new Memo();
|
|
64
|
+
context.memorable ??= ~0;
|
|
70
65
|
const p = context.precedence;
|
|
71
66
|
context.precedence = precedence;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
--resources.recursion;
|
|
76
|
-
const pos = source.length;
|
|
77
|
-
const cache = syntax && context.memo?.get(pos, syntax, state);
|
|
67
|
+
const position = source.length;
|
|
68
|
+
const state = context.state ?? 0;
|
|
69
|
+
const cache = syntax && memo.get(position, syntax, state);
|
|
78
70
|
const result: Result<T> = cache
|
|
79
71
|
? cache.length === 0
|
|
80
72
|
? undefined
|
|
81
73
|
: [cache[0], source.slice(cache[1])]
|
|
82
74
|
: parser!(source, context);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
assert(
|
|
86
|
-
resources.budget -= cost;
|
|
75
|
+
if (syntax && state & context.memorable!) {
|
|
76
|
+
cache ?? memo.set(position, syntax, state, eval(result), source.length - exec(result, '').length);
|
|
77
|
+
assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, state));
|
|
87
78
|
}
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
cache ?? context.memo.set(pos, syntax, state, eval(result), source.length - exec(result, '').length);
|
|
92
|
-
assert.deepStrictEqual(cache && cache, cache && context.memo.get(pos, syntax, state));
|
|
93
|
-
}
|
|
94
|
-
else if (result && context.memo?.length! >= pos) {
|
|
95
|
-
assert(!(state & context.backtrackable));
|
|
96
|
-
context.memo!.clear(pos);
|
|
97
|
-
}
|
|
79
|
+
if (result && !state && memo.length! >= position) {
|
|
80
|
+
assert(!(state & context.memorable!));
|
|
81
|
+
memo.clear(position);
|
|
98
82
|
}
|
|
99
83
|
context.precedence = p;
|
|
100
84
|
return result;
|
|
101
|
-
};
|
|
85
|
+
});
|
|
102
86
|
}
|
|
103
87
|
|
|
104
|
-
export function
|
|
105
|
-
export function
|
|
106
|
-
export function
|
|
107
|
-
if (typeof cost === 'function') return
|
|
88
|
+
export function creation<P extends Parser<unknown>>(parser: P): P;
|
|
89
|
+
export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
|
|
90
|
+
export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
91
|
+
if (typeof cost === 'function') return creation(1, cost);
|
|
108
92
|
assert(cost >= 0);
|
|
109
93
|
return (source, context) => {
|
|
110
|
-
const { resources = {
|
|
111
|
-
if (resources.
|
|
94
|
+
const { resources = { clock: 1, recursion: 1 } } = context;
|
|
95
|
+
if (resources.clock <= 0) throw new Error('Too many creations');
|
|
112
96
|
if (resources.recursion <= 0) throw new Error('Too much recursion');
|
|
113
97
|
--resources.recursion;
|
|
114
98
|
const result = parser!(source, context);
|
|
115
99
|
++resources.recursion;
|
|
116
100
|
if (result) {
|
|
117
|
-
resources.
|
|
101
|
+
resources.clock -= cost;
|
|
118
102
|
}
|
|
119
103
|
return result;
|
|
120
104
|
};
|
|
@@ -139,6 +123,24 @@ export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T
|
|
|
139
123
|
: undefined;
|
|
140
124
|
}
|
|
141
125
|
|
|
126
|
+
export function constraint<P extends Parser<unknown>>(state: number, parser: P): P;
|
|
127
|
+
export function constraint<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
|
|
128
|
+
export function constraint<T>(state: number, positive: boolean | Parser<T>, parser?: Parser<T>): Parser<T> {
|
|
129
|
+
if (typeof positive === 'function') {
|
|
130
|
+
parser = positive;
|
|
131
|
+
positive = true;
|
|
132
|
+
}
|
|
133
|
+
assert(state);
|
|
134
|
+
return (source, context) => {
|
|
135
|
+
const s = positive
|
|
136
|
+
? state & context.state!
|
|
137
|
+
: state & ~context.state!;
|
|
138
|
+
return s === state
|
|
139
|
+
? parser!(source, context)
|
|
140
|
+
: undefined;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
142
144
|
export function state<P extends Parser<unknown>>(state: number, parser: P): P;
|
|
143
145
|
export function state<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
|
|
144
146
|
export function state<T>(state: number, positive: boolean | Parser<T>, parser?: Parser<T>): Parser<T> {
|
|
@@ -146,6 +148,7 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
|
|
|
146
148
|
parser = positive;
|
|
147
149
|
positive = true;
|
|
148
150
|
}
|
|
151
|
+
assert(state);
|
|
149
152
|
return (source, context) => {
|
|
150
153
|
const s = context.state ?? 0;
|
|
151
154
|
context.state = positive
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { undefined } from 'spica/global';
|
|
2
2
|
import { Parser, eval, exec, check } from '../parser';
|
|
3
3
|
import { Delimiters } from './context/delimiter';
|
|
4
|
-
import { push } from 'spica/array';
|
|
4
|
+
import { unshift, push } from 'spica/array';
|
|
5
5
|
|
|
6
6
|
type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
|
|
7
7
|
|
|
@@ -32,7 +32,9 @@ export function some<T>(parser: Parser<T>, end?: string | RegExp | number, delim
|
|
|
32
32
|
assert.doesNotThrow(() => limit < 0 && check(rest, result));
|
|
33
33
|
if (!result) break;
|
|
34
34
|
nodes = nodes
|
|
35
|
-
?
|
|
35
|
+
? nodes.length < eval(result).length
|
|
36
|
+
? unshift(nodes, eval(result))
|
|
37
|
+
: push(nodes, eval(result))
|
|
36
38
|
: eval(result);
|
|
37
39
|
rest = exec(result);
|
|
38
40
|
if (limit >= 0 && source.length - rest.length > limit) break;
|
|
@@ -9,13 +9,13 @@ export type Result<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
|
9
9
|
| undefined;
|
|
10
10
|
export interface Ctx {
|
|
11
11
|
readonly resources?: {
|
|
12
|
-
|
|
12
|
+
clock: number;
|
|
13
13
|
recursion: number;
|
|
14
14
|
};
|
|
15
15
|
precedence?: number;
|
|
16
16
|
delimiters?: Delimiters;
|
|
17
17
|
state?: number;
|
|
18
|
-
|
|
18
|
+
memorable?: number;
|
|
19
19
|
memo?: Memo;
|
|
20
20
|
}
|
|
21
21
|
export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
24
24
|
let context: MarkdownParser.Context = {
|
|
25
25
|
...settings,
|
|
26
26
|
host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
27
|
-
backtrackable,
|
|
27
|
+
memorable: backtrackable,
|
|
28
28
|
};
|
|
29
29
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
30
30
|
assert(!settings.id);
|
|
@@ -247,19 +247,19 @@ describe('Unit: parser/api/parse', () => {
|
|
|
247
247
|
[...parse('('.repeat(20)).children].map(el => el.outerHTML),
|
|
248
248
|
[`<p>${'('.repeat(20)}</p>`]);
|
|
249
249
|
assert.deepStrictEqual(
|
|
250
|
-
[...parse('('.repeat(
|
|
250
|
+
[...parse('('.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
251
251
|
[
|
|
252
252
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
253
|
-
`<pre class="error" translate="no">${'('.repeat(
|
|
253
|
+
`<pre class="error" translate="no">${'('.repeat(22)}</pre>`,
|
|
254
254
|
]);
|
|
255
255
|
assert.deepStrictEqual(
|
|
256
256
|
[...parse('['.repeat(20)).children].map(el => el.outerHTML),
|
|
257
257
|
[`<p>${'['.repeat(20)}</p>`]);
|
|
258
258
|
assert.deepStrictEqual(
|
|
259
|
-
[...parse('['.repeat(
|
|
259
|
+
[...parse('['.repeat(22)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
260
260
|
[
|
|
261
261
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
262
|
-
`<pre class="error" translate="no">${'['.repeat(
|
|
262
|
+
`<pre class="error" translate="no">${'['.repeat(22)}</pre>`,
|
|
263
263
|
]);
|
|
264
264
|
assert.deepStrictEqual(
|
|
265
265
|
[...parse('['.repeat(17) + '\na').children].map(el => el.outerHTML),
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
30
30
|
...context?.resources && {
|
|
31
31
|
resources: context.resources,
|
|
32
32
|
},
|
|
33
|
-
backtrackable,
|
|
33
|
+
memorable: backtrackable,
|
|
34
34
|
};
|
|
35
35
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
36
36
|
const node = frag();
|
|
@@ -13,13 +13,14 @@ describe('Unit: parser/autolink', () => {
|
|
|
13
13
|
assert.deepStrictEqual(inspect(parser('@a#b')), [['<a href="/@a?ch=b" class="channel">@a#b</a>'], '']);
|
|
14
14
|
assert.deepStrictEqual(inspect(parser('\\\n')), [['\\', '<br>'], '']);
|
|
15
15
|
assert.deepStrictEqual(inspect(parser('a#b')), [['a#b'], '']);
|
|
16
|
-
assert.deepStrictEqual(inspect(parser('0a#b')), [['
|
|
16
|
+
assert.deepStrictEqual(inspect(parser('0a#b')), [['0a#b'], '']);
|
|
17
17
|
assert.deepStrictEqual(inspect(parser('あ#b')), [['あ#b'], '']);
|
|
18
18
|
assert.deepStrictEqual(inspect(parser('あい#b')), [['あ', 'い#b'], '']);
|
|
19
|
-
assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0a
|
|
19
|
+
assert.deepStrictEqual(inspect(parser('0aあ#b')), [['0aあ#b'], '']);
|
|
20
20
|
assert.deepStrictEqual(inspect(parser('0aあい#b')), [['0a', 'あ', 'い#b'], '']);
|
|
21
21
|
assert.deepStrictEqual(inspect(parser('a\n#b')), [['a', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser('a\\\n#b')), [['a', '\\', '<br>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser('0a>>b')), [['0a>>b'], '']);
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
});
|
package/src/parser/autolink.ts
CHANGED
|
@@ -5,7 +5,23 @@ import { linebreak, unescsource } from './source';
|
|
|
5
5
|
|
|
6
6
|
export import AutolinkParser = MarkdownParser.AutolinkParser;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const delimiter = /[@#>0-9A-Za-z\n]|\S[#>]/;
|
|
9
|
+
|
|
10
|
+
export const autolink: AutolinkParser = (source, context) => {
|
|
11
|
+
if (source === '') return;
|
|
12
|
+
assert(source[0] !== '\x1B');
|
|
13
|
+
const i = source.search(delimiter);
|
|
14
|
+
switch (i) {
|
|
15
|
+
case -1:
|
|
16
|
+
return [[source], ''];
|
|
17
|
+
case 0:
|
|
18
|
+
return parser(source, context);
|
|
19
|
+
default:
|
|
20
|
+
return [[source.slice(0, i)], source.slice(i)];
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const parser: AutolinkParser = lazy(() => union([
|
|
9
25
|
autolink_,
|
|
10
26
|
linebreak,
|
|
11
27
|
unescsource
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BlockquoteParser } from '../block';
|
|
2
|
-
import { union, some,
|
|
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
5
|
import { parse } from '../api/parse';
|
|
@@ -19,7 +19,7 @@ const indent = block(open(opener, some(contentline, /^>(?:$|\s)/)), false);
|
|
|
19
19
|
const unindent = (source: string) => source.replace(/(^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '$1');
|
|
20
20
|
|
|
21
21
|
const source: BlockquoteParser.SourceParser = lazy(() => fmap(
|
|
22
|
-
some(
|
|
22
|
+
some(creation(union([
|
|
23
23
|
rewrite(
|
|
24
24
|
indent,
|
|
25
25
|
convert(unindent, source)),
|
|
@@ -30,11 +30,11 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
|
|
|
30
30
|
ns => [html('blockquote', ns)]));
|
|
31
31
|
|
|
32
32
|
const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
|
|
33
|
-
some(
|
|
33
|
+
some(creation(union([
|
|
34
34
|
rewrite(
|
|
35
35
|
indent,
|
|
36
36
|
convert(unindent, markdown)),
|
|
37
|
-
|
|
37
|
+
creation(99,
|
|
38
38
|
rewrite(
|
|
39
39
|
some(contentline, opener),
|
|
40
40
|
convert(unindent, (source, context) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DListParser } from '../block';
|
|
2
|
-
import { union, inits, some,
|
|
2
|
+
import { union, inits, some, creation, state, block, line, validate, rewrite, open, trimEnd, lazy, fmap } from '../../combinator';
|
|
3
3
|
import { inline, indexee, indexer } from '../inline';
|
|
4
4
|
import { anyline } from '../source';
|
|
5
5
|
import { State } from '../context';
|
|
@@ -17,13 +17,13 @@ export const dlist: DListParser = lazy(() => block(localize(fmap(validate(
|
|
|
17
17
|
]))),
|
|
18
18
|
es => [html('dl', fillTrailingDescription(es))]))));
|
|
19
19
|
|
|
20
|
-
const term: DListParser.TermParser =
|
|
20
|
+
const term: DListParser.TermParser = creation(line(indexee(fmap(open(
|
|
21
21
|
/^~[^\S\n]+(?=\S)/,
|
|
22
22
|
visualize(trimBlank(some(union([indexer, inline])))),
|
|
23
23
|
true),
|
|
24
24
|
ns => [html('dt', defrag(ns))]))));
|
|
25
25
|
|
|
26
|
-
const desc: DListParser.DescriptionParser =
|
|
26
|
+
const desc: DListParser.DescriptionParser = creation(block(fmap(open(
|
|
27
27
|
/^:[^\S\n]+(?=\S)|/,
|
|
28
28
|
rewrite(
|
|
29
29
|
some(anyline, /^[~:][^\S\n]+\S/),
|
|
@@ -2,7 +2,7 @@ import { undefined, BigInt, Array } from 'spica/global';
|
|
|
2
2
|
import { max, min, isArray } from 'spica/alias';
|
|
3
3
|
import { ExtensionParser } from '../../block';
|
|
4
4
|
import { Tree, eval } from '../../../combinator/data/parser';
|
|
5
|
-
import { union, subsequence, inits, some,
|
|
5
|
+
import { union, subsequence, inits, some, creation, block, line, validate, fence, rewrite, open, clear, convert, trim, dup, lazy, fmap } from '../../../combinator';
|
|
6
6
|
import { inline } from '../../inline';
|
|
7
7
|
import { str, anyline, emptyline, contentline } from '../../source';
|
|
8
8
|
import { localize } from '../../locale';
|
|
@@ -79,7 +79,7 @@ const align: AlignParser = line(fmap(
|
|
|
79
79
|
|
|
80
80
|
const delimiter = /^[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|^[#:](?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/;
|
|
81
81
|
|
|
82
|
-
const head: CellParser.HeadParser =
|
|
82
|
+
const head: CellParser.HeadParser = creation(block(fmap(open(
|
|
83
83
|
str(/^#(?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/),
|
|
84
84
|
rewrite(
|
|
85
85
|
inits([
|
|
@@ -91,7 +91,7 @@ const head: CellParser.HeadParser = creator(block(fmap(open(
|
|
|
91
91
|
ns => [html('th', attributes(ns.shift()! as string), defrag(ns))]),
|
|
92
92
|
false));
|
|
93
93
|
|
|
94
|
-
const data: CellParser.DataParser =
|
|
94
|
+
const data: CellParser.DataParser = creation(block(fmap(open(
|
|
95
95
|
str(/^:(?:(?!:\D|0)\d*:(?!0)\d*)?!*(?=\s)/),
|
|
96
96
|
rewrite(
|
|
97
97
|
inits([
|
|
@@ -103,7 +103,7 @@ const data: CellParser.DataParser = creator(block(fmap(open(
|
|
|
103
103
|
ns => [html('td', attributes(ns.shift()! as string), defrag(ns))]),
|
|
104
104
|
false));
|
|
105
105
|
|
|
106
|
-
const dataline: CellParser.DatalineParser =
|
|
106
|
+
const dataline: CellParser.DatalineParser = creation(line(
|
|
107
107
|
rewrite(
|
|
108
108
|
contentline,
|
|
109
109
|
union([
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IListParser } from '../block';
|
|
2
|
-
import { union, inits, some,
|
|
2
|
+
import { union, inits, some, creation, state, block, line, validate, indent, open, fallback, lazy, fmap } from '../../combinator';
|
|
3
3
|
import { ulist_, fillFirstLine } from './ulist';
|
|
4
4
|
import { olist_, invalid } from './olist';
|
|
5
5
|
import { inline } from '../inline';
|
|
@@ -13,7 +13,7 @@ export const ilist: IListParser = lazy(() => block(validate(
|
|
|
13
13
|
|
|
14
14
|
export const ilist_: IListParser = lazy(() => block(fmap(validate(
|
|
15
15
|
/^[-+*](?:$|\s)/,
|
|
16
|
-
some(
|
|
16
|
+
some(creation(union([
|
|
17
17
|
fmap(fallback(
|
|
18
18
|
inits([
|
|
19
19
|
line(open(/^[-+*](?:$|\s)/, some(inline), true)),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { undefined } from 'spica/global';
|
|
2
2
|
import { OListParser } from '../block';
|
|
3
|
-
import { union, inits, subsequence, some,
|
|
3
|
+
import { union, inits, subsequence, some, creation, state, block, line, validate, indent, focus, rewrite, open, match, fallback, lazy, fmap } from '../../combinator';
|
|
4
4
|
import { checkbox, ulist_, fillFirstLine } from './ulist';
|
|
5
5
|
import { ilist_ } from './ilist';
|
|
6
6
|
import { inline, indexee, indexer } from '../inline';
|
|
@@ -36,7 +36,7 @@ export const olist_: OListParser = lazy(() => block(union([
|
|
|
36
36
|
])));
|
|
37
37
|
|
|
38
38
|
const list = (type: string, form: string): OListParser.ListParser => fmap(
|
|
39
|
-
some(
|
|
39
|
+
some(creation(union([
|
|
40
40
|
indexee(fmap(fallback(
|
|
41
41
|
inits([
|
|
42
42
|
line(open(heads[form], subsequence([checkbox, trimBlank(some(union([indexer, inline])))]), true)),
|
|
@@ -45,13 +45,15 @@ describe('Unit: parser/block/paragraph', () => {
|
|
|
45
45
|
assert.deepStrictEqual(inspect(parser('>>11 a')), [['<p><a href="?at=11" class="anchor">>>11</a> a</p>'], '']);
|
|
46
46
|
assert.deepStrictEqual(inspect(parser('>>>11 a')), [['<p>><a href="?at=11" class="anchor">>>11</a> a</p>'], '']);
|
|
47
47
|
assert.deepStrictEqual(inspect(parser('>> a\n>>1')), [['<p>>> a<br><a href="?at=1" class="anchor">>>1</a></p>'], '']);
|
|
48
|
-
assert.deepStrictEqual(inspect(parser('a>>1')), [['<p>a
|
|
48
|
+
assert.deepStrictEqual(inspect(parser('a>>1')), [['<p>a>>1</p>'], '']);
|
|
49
|
+
assert.deepStrictEqual(inspect(parser('ab>>1')), [['<p>ab>>1</p>'], '']);
|
|
49
50
|
assert.deepStrictEqual(inspect(parser('a >>1')), [['<p>a <a href="?at=1" class="anchor">>>1</a></p>'], '']);
|
|
50
51
|
assert.deepStrictEqual(inspect(parser('a\n>>1')), [['<p>a<br><a href="?at=1" class="anchor">>>1</a></p>'], '']);
|
|
51
52
|
assert.deepStrictEqual(inspect(parser('a\n>>1\nb')), [['<p>a<br><a href="?at=1" class="anchor">>>1</a><br>b</p>'], '']);
|
|
52
53
|
assert.deepStrictEqual(inspect(parser('a\n>> b\nc')), [['<p>a<br>>> b<br>c</p>'], '']);
|
|
53
54
|
assert.deepStrictEqual(inspect(parser('\t>>1')), [['<p>\t<a href="?at=1" class="anchor">>>1</a></p>'], '']);
|
|
54
55
|
assert.deepStrictEqual(inspect(parser('\t>>>1')), [['<p>\t><a href="?at=1" class="anchor">>>1</a></p>'], '']);
|
|
56
|
+
assert.deepStrictEqual(inspect(parser('あ>>1')), [['<p>あ<a href="?at=1" class="anchor">>>1</a></p>'], '']);
|
|
55
57
|
});
|
|
56
58
|
|
|
57
59
|
it('comment', () => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ReplyParser } from '../../block';
|
|
2
|
-
import { union, tails,
|
|
2
|
+
import { union, tails, creation, line, validate, focus, reverse, fmap } from '../../../combinator';
|
|
3
3
|
import { anchor } from '../../inline/autolink/anchor';
|
|
4
4
|
import { str } from '../../source';
|
|
5
5
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
6
6
|
|
|
7
|
-
export const cite: ReplyParser.CiteParser =
|
|
7
|
+
export const cite: ReplyParser.CiteParser = creation(line(fmap(validate(
|
|
8
8
|
'>>',
|
|
9
9
|
reverse(tails([
|
|
10
10
|
str(/^>*(?=>>[^>\s]+[^\S\n]*(?:$|\n))/),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReplyParser } from '../../block';
|
|
2
2
|
import { eval } from '../../../combinator/data/parser';
|
|
3
|
-
import { union, some,
|
|
3
|
+
import { union, some, creation, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
|
|
4
4
|
import { math } from '../../inline/math';
|
|
5
5
|
import { str, anyline } from '../../source';
|
|
6
6
|
import { autolink } from '../../autolink';
|
|
@@ -8,7 +8,7 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
8
8
|
|
|
9
9
|
export const syntax = /^>+(?=[^\S\n])|^>(?=[^\s>])|^>+(?=[^\s>])(?![0-9a-z]+(?:-[0-9a-z]+)*(?![0-9A-Za-z@#:]))/;
|
|
10
10
|
|
|
11
|
-
export const quote: ReplyParser.QuoteParser = lazy(() =>
|
|
11
|
+
export const quote: ReplyParser.QuoteParser = lazy(() => creation(block(fmap(validate(
|
|
12
12
|
'>',
|
|
13
13
|
union([
|
|
14
14
|
rewrite(
|
|
@@ -58,7 +58,7 @@ const qblock: ReplyParser.QuoteParser.BlockParser = (source, context) => {
|
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
60
60
|
if (child.classList.contains('cite') || child.classList.contains('quote')) {
|
|
61
|
-
context.resources && (context.resources.
|
|
61
|
+
context.resources && (context.resources.clock -= child.childNodes.length);
|
|
62
62
|
nodes.splice(i, 1, ...child.childNodes as NodeListOf<HTMLElement>);
|
|
63
63
|
--i;
|
|
64
64
|
continue;
|