securemark 0.290.2 → 0.291.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 +4 -0
- package/design.md +48 -2
- package/dist/index.js +215 -121
- package/markdown.d.ts +6 -17
- package/package.json +1 -1
- package/src/combinator/control/manipulation/surround.ts +17 -13
- package/src/combinator/data/parser/context/delimiter.ts +2 -2
- package/src/combinator/data/parser/context.ts +4 -3
- package/src/combinator/data/parser/some.ts +3 -3
- package/src/combinator/data/parser.ts +5 -3
- package/src/parser/api/parse.test.ts +34 -30
- package/src/parser/block/dlist.ts +1 -1
- package/src/parser/block/heading.ts +2 -2
- package/src/parser/block/mediablock.ts +2 -2
- package/src/parser/block/pagebreak.ts +1 -1
- package/src/parser/block/reply.ts +1 -1
- package/src/parser/block.ts +3 -1
- package/src/parser/header.ts +1 -1
- package/src/parser/inline/annotation.ts +4 -5
- package/src/parser/inline/autolink/account.ts +1 -1
- package/src/parser/inline/autolink/anchor.ts +1 -1
- package/src/parser/inline/autolink/channel.ts +1 -1
- package/src/parser/inline/autolink/email.ts +1 -1
- package/src/parser/inline/autolink/hashnum.ts +1 -1
- package/src/parser/inline/autolink/hashtag.ts +1 -1
- package/src/parser/inline/autolink/url.test.ts +8 -2
- package/src/parser/inline/autolink/url.ts +5 -6
- package/src/parser/inline/bracket.ts +25 -3
- package/src/parser/inline/code.ts +2 -2
- package/src/parser/inline/extension/index.ts +42 -26
- package/src/parser/inline/extension/indexee.ts +6 -3
- package/src/parser/inline/extension/label.ts +1 -1
- package/src/parser/inline/html.test.ts +22 -19
- package/src/parser/inline/html.ts +82 -78
- package/src/parser/inline/link.ts +12 -9
- package/src/parser/inline/mark.ts +1 -1
- package/src/parser/inline/math.test.ts +1 -0
- package/src/parser/inline/math.ts +18 -9
- package/src/parser/inline/media.test.ts +3 -3
- package/src/parser/inline/media.ts +14 -6
- package/src/parser/inline/reference.ts +67 -10
- package/src/parser/inline/ruby.ts +6 -5
- package/src/parser/inline/shortmedia.ts +1 -1
- package/src/parser/inline.ts +4 -19
- package/src/parser/segment.test.ts +3 -2
- package/src/parser/segment.ts +2 -2
- package/src/parser/source/escapable.ts +1 -1
- package/src/parser/source/text.ts +2 -2
- package/src/parser/source/unescapable.ts +1 -1
- package/src/parser/util.ts +14 -4
- package/src/parser/visibility.ts +1 -0
package/markdown.d.ts
CHANGED
|
@@ -723,7 +723,7 @@ export namespace MarkdownParser {
|
|
|
723
723
|
// [#index]
|
|
724
724
|
// [#index|signature]
|
|
725
725
|
Inline<'extension/index'>,
|
|
726
|
-
Parser<
|
|
726
|
+
Parser<string | HTMLElement, Context, [
|
|
727
727
|
InlineParser,
|
|
728
728
|
IndexParser.SignatureParser,
|
|
729
729
|
]> {
|
|
@@ -736,21 +736,10 @@ export namespace MarkdownParser {
|
|
|
736
736
|
]> {
|
|
737
737
|
}
|
|
738
738
|
export namespace SignatureParser {
|
|
739
|
-
export interface
|
|
740
|
-
Inline<'extension/index/signature/
|
|
739
|
+
export interface InternalParser extends
|
|
740
|
+
Inline<'extension/index/signature/internal'>,
|
|
741
741
|
Parser<string, Context, [
|
|
742
|
-
|
|
743
|
-
BracketParser,
|
|
744
|
-
SourceParser.TxtParser,
|
|
745
|
-
]>,
|
|
746
|
-
Parser<string, Context, [
|
|
747
|
-
BracketParser,
|
|
748
|
-
SourceParser.TxtParser,
|
|
749
|
-
]>,
|
|
750
|
-
Parser<string, Context, [
|
|
751
|
-
BracketParser,
|
|
752
|
-
SourceParser.TxtParser,
|
|
753
|
-
]>,
|
|
742
|
+
UnsafeHTMLEntityParser,
|
|
754
743
|
SourceParser.TxtParser,
|
|
755
744
|
]> {
|
|
756
745
|
}
|
|
@@ -933,7 +922,6 @@ export namespace MarkdownParser {
|
|
|
933
922
|
export interface OptionParser extends
|
|
934
923
|
Inline<'media/parameter/option'>,
|
|
935
924
|
Parser<string, Context, [
|
|
936
|
-
SourceParser.StrParser,
|
|
937
925
|
SourceParser.StrParser,
|
|
938
926
|
LinkParser.ParameterParser.OptionParser,
|
|
939
927
|
]> {
|
|
@@ -982,6 +970,7 @@ export namespace MarkdownParser {
|
|
|
982
970
|
Inline<'html/attribute'>,
|
|
983
971
|
Parser<string, Context, [
|
|
984
972
|
SourceParser.StrParser,
|
|
973
|
+
SourceParser.StrParser,
|
|
985
974
|
]> {
|
|
986
975
|
}
|
|
987
976
|
}
|
|
@@ -1057,7 +1046,7 @@ export namespace MarkdownParser {
|
|
|
1057
1046
|
MathParser.BracketParser,
|
|
1058
1047
|
Parser<string, Context, [
|
|
1059
1048
|
MathParser.BracketParser,
|
|
1060
|
-
SourceParser.
|
|
1049
|
+
SourceParser.EscapableSourceParser,
|
|
1061
1050
|
]>,
|
|
1062
1051
|
]> {
|
|
1063
1052
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Parser, Input, Result, Ctx, Node, Context, SubParsers, SubNode, IntermediateParser, eval, exec, check } from '../../data/parser';
|
|
2
|
+
import { consume } from '../../../combinator';
|
|
2
3
|
import { unshift, push } from 'spica/array';
|
|
3
4
|
|
|
4
5
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
@@ -50,21 +51,21 @@ export function surround<N>(
|
|
|
50
51
|
const sme_ = source;
|
|
51
52
|
if (sme_ === '') return;
|
|
52
53
|
const { linebreak } = context;
|
|
53
|
-
context.linebreak =
|
|
54
|
+
context.linebreak = 0;
|
|
54
55
|
const resultS = opener({ source: sme_, context });
|
|
55
56
|
assert(check(sme_, resultS, false));
|
|
56
57
|
if (resultS === undefined) return void revert(context, linebreak);
|
|
57
58
|
const nodesS = eval(resultS);
|
|
58
59
|
const me_ = exec(resultS);
|
|
59
|
-
if (
|
|
60
|
+
if (isBacktrack(context, backtracks, sme_, sme_.length - me_.length)) return void revert(context, linebreak);
|
|
60
61
|
const resultM = me_ !== '' ? parser({ source: me_, context }) : undefined;
|
|
61
62
|
assert(check(me_, resultM));
|
|
62
63
|
const nodesM = eval(resultM);
|
|
63
|
-
const e_ = exec(resultM
|
|
64
|
+
const e_ = exec(resultM) ?? me_;
|
|
64
65
|
const resultE = nodesM || optional ? closer({ source: e_, context }) : undefined;
|
|
65
66
|
assert(check(e_, resultE, false));
|
|
66
67
|
const nodesE = eval(resultE);
|
|
67
|
-
const rest = exec(resultE
|
|
68
|
+
const rest = exec(resultE) ?? e_;
|
|
68
69
|
nodesE || setBacktrack(context, backtracks, sme_.length);
|
|
69
70
|
if (!nodesM && !optional) return void revert(context, linebreak);
|
|
70
71
|
if (rest.length === sme_.length) return void revert(context, linebreak);
|
|
@@ -81,7 +82,7 @@ export function surround<N>(
|
|
|
81
82
|
? g([nodesS, nodesM!, me_], rest, context)
|
|
82
83
|
: undefined;
|
|
83
84
|
if (result) {
|
|
84
|
-
context.linebreak
|
|
85
|
+
context.linebreak ||= linebreak;
|
|
85
86
|
}
|
|
86
87
|
else {
|
|
87
88
|
revert(context, linebreak);
|
|
@@ -119,16 +120,18 @@ export function close<N>(
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
const statesize = 2;
|
|
122
|
-
export function
|
|
123
|
+
export function isBacktrack(
|
|
123
124
|
context: Ctx,
|
|
124
125
|
backtracks: readonly number[],
|
|
125
126
|
source: string,
|
|
126
127
|
length: number = 1,
|
|
127
128
|
): boolean {
|
|
128
|
-
if (length
|
|
129
|
+
if (length === 0 || source.length === 0) return false;
|
|
130
|
+
for (const backtrack of backtracks) {
|
|
129
131
|
if (backtrack & 1) {
|
|
130
132
|
const { backtracks = {}, offset = 0 } = context;
|
|
131
133
|
for (let i = 0; i < length; ++i) {
|
|
134
|
+
assert(i < source.length);
|
|
132
135
|
if (source[i] !== source[0]) break;
|
|
133
136
|
const pos = source.length - i + offset - 1;
|
|
134
137
|
assert(pos >= 0);
|
|
@@ -145,8 +148,9 @@ export function setBacktrack(
|
|
|
145
148
|
position: number,
|
|
146
149
|
length: number = 1,
|
|
147
150
|
): void {
|
|
148
|
-
if (length
|
|
149
|
-
|
|
151
|
+
if (length === 0 || position === 0) return;
|
|
152
|
+
for (const backtrack of backtracks) {
|
|
153
|
+
if (backtrack & 2 && position !== 0) {
|
|
150
154
|
const { backtracks = {}, offset = 0 } = context;
|
|
151
155
|
for (let i = 0; i < length; ++i) {
|
|
152
156
|
const pos = position - i + offset - 1;
|
|
@@ -162,11 +166,11 @@ function match(pattern: string | RegExp): (input: Input) => [never[], string] |
|
|
|
162
166
|
case 'string':
|
|
163
167
|
return ({ source }) => source.slice(0, pattern.length) === pattern ? [[], source.slice(pattern.length)] : undefined;
|
|
164
168
|
case 'object':
|
|
165
|
-
return ({ source }) => {
|
|
169
|
+
return ({ source, context }) => {
|
|
166
170
|
const m = source.match(pattern);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
if (m === null) return;
|
|
172
|
+
consume(m[0].length, context);
|
|
173
|
+
return [[], source.slice(m[0].length)];
|
|
170
174
|
};
|
|
171
175
|
}
|
|
172
176
|
}
|
|
@@ -38,11 +38,11 @@ export class Delimiters {
|
|
|
38
38
|
return source => pattern.test(source) || undefined;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
private readonly
|
|
41
|
+
private readonly tree: Record<number, Delimiter[]> = {};
|
|
42
42
|
private readonly map: Map<string, Delimiter[]> = new Map();
|
|
43
43
|
private registry(signature: number | string): Delimiter[] {
|
|
44
44
|
if (typeof signature === 'number') {
|
|
45
|
-
return this.
|
|
45
|
+
return this.tree[signature] ??= [];
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
48
48
|
const ds = this.map.get(signature);
|
|
@@ -50,7 +50,8 @@ function apply<N>(parser: Parser<N>, source: string, context: Ctx, changes: read
|
|
|
50
50
|
switch (prop) {
|
|
51
51
|
case 'resources':
|
|
52
52
|
assert(reset);
|
|
53
|
-
|
|
53
|
+
// プロトタイプに戻ることで戻す
|
|
54
|
+
continue;
|
|
54
55
|
}
|
|
55
56
|
context[prop] = values[i];
|
|
56
57
|
values[i] = undefined;
|
|
@@ -135,11 +136,11 @@ export function state<N>(state: number, positive: boolean | Parser<N>, parser?:
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
export function constraint<P extends Parser<unknown>>(state: number, parser: P): P;
|
|
138
|
-
export function constraint<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
|
|
139
|
+
//export function constraint<P extends Parser<unknown>>(state: number, positive: boolean, parser: P): P;
|
|
139
140
|
export function constraint<N>(state: number, positive: boolean | Parser<N>, parser?: Parser<N>): Parser<N> {
|
|
140
141
|
if (typeof positive === 'function') {
|
|
141
142
|
parser = positive;
|
|
142
|
-
positive =
|
|
143
|
+
positive = false;
|
|
143
144
|
}
|
|
144
145
|
assert(state);
|
|
145
146
|
assert(parser = parser!);
|
|
@@ -6,7 +6,7 @@ type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number,
|
|
|
6
6
|
|
|
7
7
|
export function some<P extends Parser<unknown>>(parser: P, limit?: number): P;
|
|
8
8
|
export function some<P extends Parser<unknown>>(parser: P, end?: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
|
|
9
|
-
export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delimiters: readonly DelimiterOption[] = [], limit =
|
|
9
|
+
export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delimiters: readonly DelimiterOption[] = [], limit = 0): Parser<N> {
|
|
10
10
|
if (typeof end === 'number') return some(parser, undefined, delimiters, end);
|
|
11
11
|
assert(parser);
|
|
12
12
|
assert([end].concat(delimiters.map(o => o[0])).every(d => d instanceof RegExp ? !d.flags.match(/[gmy]/) && d.source.startsWith('^') : true));
|
|
@@ -31,7 +31,7 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
|
|
|
31
31
|
if (match(rest)) break;
|
|
32
32
|
if (context.delimiters?.match(rest, context)) break;
|
|
33
33
|
const result = parser({ source: rest, context });
|
|
34
|
-
assert.doesNotThrow(() => limit
|
|
34
|
+
assert.doesNotThrow(() => limit === 0 && check(rest, result));
|
|
35
35
|
if (result === undefined) break;
|
|
36
36
|
nodes = nodes
|
|
37
37
|
? nodes.length < eval(result).length / 8
|
|
@@ -39,7 +39,7 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
|
|
|
39
39
|
: push(nodes, eval(result))
|
|
40
40
|
: eval(result);
|
|
41
41
|
rest = exec(result);
|
|
42
|
-
if (limit
|
|
42
|
+
if (limit > 0 && source.length - rest.length > limit) break;
|
|
43
43
|
}
|
|
44
44
|
if (delims.length > 0) {
|
|
45
45
|
context.delimiters!.pop(delims.length);
|
|
@@ -19,7 +19,9 @@ export interface Ctx {
|
|
|
19
19
|
precedence?: number;
|
|
20
20
|
delimiters?: Delimiters;
|
|
21
21
|
state?: number;
|
|
22
|
-
|
|
22
|
+
// Objectの内部実装を利用する。
|
|
23
|
+
// 探索木を直接使用する場合は探索速度が重要で挿入は相対的に少なく削除は不要かつ不確実であるため
|
|
24
|
+
// AVL木が適当と思われる。
|
|
23
25
|
backtracks?: Record<number, number>;
|
|
24
26
|
linebreak?: number;
|
|
25
27
|
recent?: string[];
|
|
@@ -42,8 +44,8 @@ function eval_<N>(result: Result<N>, default_?: N[]): N[] | undefined {
|
|
|
42
44
|
: default_;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
export function exec(result: NonNullable<Result<unknown>>, default_?:
|
|
46
|
-
export function exec(result: Result<unknown>, default_:
|
|
47
|
+
export function exec(result: NonNullable<Result<unknown>>, default_?: ''): string;
|
|
48
|
+
export function exec(result: Result<unknown>, default_: ''): string
|
|
47
49
|
export function exec(result: Result<unknown>, default_?: undefined): string | undefined;
|
|
48
50
|
export function exec(result: Result<unknown>, default_?: string): string | undefined {
|
|
49
51
|
return result
|
|
@@ -288,44 +288,47 @@ describe('Unit: parser/api/parse', () => {
|
|
|
288
288
|
|
|
289
289
|
it('recursion', () => {
|
|
290
290
|
assert.deepStrictEqual(
|
|
291
|
-
[...parse(`${'{'.repeat(20)}
|
|
292
|
-
[`<p>${'{'.repeat(20)}
|
|
291
|
+
[...parse(`${'{'.repeat(20)}0`).children].map(el => el.outerHTML),
|
|
292
|
+
[`<p>${'{'.repeat(20)}0</p>`]);
|
|
293
293
|
assert.deepStrictEqual(
|
|
294
|
-
[...parse(`${'{'.repeat(21)}
|
|
294
|
+
[...parse(`${'{'.repeat(21)}0`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
295
295
|
[
|
|
296
296
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
297
|
-
`<pre class="error" translate="no">${'{'.repeat(21)}
|
|
297
|
+
`<pre class="error" translate="no">${'{'.repeat(21)}0</pre>`,
|
|
298
298
|
]);
|
|
299
299
|
assert.deepStrictEqual(
|
|
300
|
-
[...parse(`${'('.repeat(20)}
|
|
301
|
-
[`<p>${'('.repeat(20)}
|
|
300
|
+
[...parse(`${'('.repeat(20)}0`).children].map(el => el.outerHTML),
|
|
301
|
+
[`<p>${'('.repeat(20)}0</p>`]);
|
|
302
302
|
assert.deepStrictEqual(
|
|
303
|
-
[...parse(`${'('.repeat(21)}
|
|
303
|
+
[...parse(`${'('.repeat(21)}0`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
304
304
|
[
|
|
305
305
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
306
|
-
`<pre class="error" translate="no">${'('.repeat(21)}
|
|
306
|
+
`<pre class="error" translate="no">${'('.repeat(21)}0</pre>`,
|
|
307
307
|
]);
|
|
308
308
|
assert.deepStrictEqual(
|
|
309
|
-
[...parse(`${'['.repeat(20)}
|
|
310
|
-
[`<p>${'['.repeat(20)}
|
|
309
|
+
[...parse(`${'['.repeat(20)}0`).children].map(el => el.outerHTML),
|
|
310
|
+
[`<p>${'['.repeat(20)}0</p>`]);
|
|
311
311
|
assert.deepStrictEqual(
|
|
312
|
-
[...parse(`${'['.repeat(21)}
|
|
312
|
+
[...parse(`${'['.repeat(21)}0`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
313
313
|
[
|
|
314
314
|
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
315
|
-
`<pre class="error" translate="no">${'['.repeat(21)}
|
|
315
|
+
`<pre class="error" translate="no">${'['.repeat(21)}0</pre>`,
|
|
316
316
|
]);
|
|
317
|
-
assert.deepStrictEqual(
|
|
318
|
-
[...parse(`${'['.repeat(20)}\na`).children].map(el => el.outerHTML),
|
|
319
|
-
[`<p>${'['.repeat(20)}<br>a</p>`]);
|
|
320
317
|
});
|
|
321
318
|
|
|
322
319
|
it('recovery', () => {
|
|
323
320
|
assert.deepStrictEqual(
|
|
324
|
-
[...parse(`${'
|
|
321
|
+
[...parse(`${'['.repeat(20)}0\n\n[a]`).children].map(el => el.outerHTML),
|
|
325
322
|
[
|
|
326
|
-
`<
|
|
327
|
-
|
|
328
|
-
|
|
323
|
+
`<p>${'['.repeat(20)}0</p>`,
|
|
324
|
+
'<p>[a]</p>',
|
|
325
|
+
]);
|
|
326
|
+
assert.deepStrictEqual(
|
|
327
|
+
[...parse(`${'['.repeat(21)}0\n\n[a]`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
328
|
+
[
|
|
329
|
+
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
330
|
+
`<pre class="error" translate="no">${'['.repeat(21)}0\n</pre>`,
|
|
331
|
+
'<p>[a]</p>',
|
|
329
332
|
]);
|
|
330
333
|
});
|
|
331
334
|
|
|
@@ -350,22 +353,23 @@ describe('Unit: parser/api/parse', () => {
|
|
|
350
353
|
|
|
351
354
|
it('backtrack', function () {
|
|
352
355
|
this.timeout(5000);
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
// 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して5倍遅い程度。
|
|
357
|
+
// 11n = link + link + template + annotation/reference + link +
|
|
358
|
+
// code + signature * 2 + url/math + ruby + text
|
|
359
|
+
const source = `${'.'.repeat(6 + 0)}!{ !{!{{{((([[[\`[#.|$[${'.'.repeat(9082)}]]]]`;
|
|
360
|
+
assert.deepStrictEqual(
|
|
361
|
+
[...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
|
|
362
|
+
.map(el => el.tagName),
|
|
363
|
+
['P', 'OL']);
|
|
358
364
|
});
|
|
359
365
|
|
|
360
366
|
it('backtrack error', function () {
|
|
361
367
|
this.timeout(5000);
|
|
362
|
-
const source = `${'.'.repeat(
|
|
368
|
+
const source = `${'.'.repeat(6 + 1)}!{ !{!{{{((([[[\`[#.|$[${'.'.repeat(9082)}]]]]`;
|
|
363
369
|
assert.deepStrictEqual(
|
|
364
|
-
[...parse(source
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
`<pre class="error" translate="no">${source.slice(0, 1000 - 3)}...</pre>`,
|
|
368
|
-
]);
|
|
370
|
+
[...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
|
|
371
|
+
.map(el => el.tagName),
|
|
372
|
+
['H1', 'PRE']);
|
|
369
373
|
});
|
|
370
374
|
|
|
371
375
|
});
|
|
@@ -11,7 +11,7 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
11
11
|
export const dlist: DListParser = lazy(() => block(fmap(validate(
|
|
12
12
|
/^~[^\S\n]+(?=\S)/,
|
|
13
13
|
some(inits([
|
|
14
|
-
state(State.annotation | State.reference | State.index | State.label | State.link
|
|
14
|
+
state(State.annotation | State.reference | State.index | State.label | State.link,
|
|
15
15
|
some(term)),
|
|
16
16
|
some(desc),
|
|
17
17
|
]))),
|
|
@@ -9,11 +9,11 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
9
9
|
|
|
10
10
|
export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
|
|
11
11
|
/^#+[^\S\n]+\S[^\n]*(?:\n#+(?!\S)[^\n]*)*(?:$|\n)/,
|
|
12
|
-
some(line(({ source }) => [[source], '']))
|
|
12
|
+
some(line(({ source }) => [[source], ''])))));
|
|
13
13
|
|
|
14
14
|
export const heading: HeadingParser = block(rewrite(segment,
|
|
15
15
|
// その他の表示制御は各所のCSSで行う。
|
|
16
|
-
state(State.annotation | State.reference | State.index | State.label | State.link
|
|
16
|
+
state(State.annotation | State.reference | State.index | State.label | State.link,
|
|
17
17
|
line(indexee(fmap(union([
|
|
18
18
|
open(
|
|
19
19
|
str(/^##+/),
|
|
@@ -15,9 +15,9 @@ export const mediablock: MediaBlockParser = block(validate(['[!', '!'], fmap(
|
|
|
15
15
|
medialink,
|
|
16
16
|
media,
|
|
17
17
|
shortmedia,
|
|
18
|
-
]), ({ source }) => [[html('
|
|
18
|
+
]), ({ source }) => [[html('span', {
|
|
19
19
|
class: 'invalid',
|
|
20
20
|
...invalid('mediablock', 'syntax', 'Not media syntax'),
|
|
21
|
-
}, source.replace('\n', ''))]
|
|
21
|
+
}, source.replace('\n', ''))], '']))),
|
|
22
22
|
]),
|
|
23
23
|
ns => [html('div', ns)])));
|
|
@@ -16,6 +16,6 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
|
|
|
16
16
|
quote,
|
|
17
17
|
rewrite(
|
|
18
18
|
some(anyline, delimiter),
|
|
19
|
-
visualize(lineable(some(inline),
|
|
19
|
+
visualize(lineable(some(inline), 1))),
|
|
20
20
|
])),
|
|
21
21
|
ns => [html('p', trimBlankNodeEnd(defrag(ns)))])));
|
package/src/parser/block.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MarkdownParser } from '../../markdown';
|
|
2
2
|
import { Recursion, Command } from './context';
|
|
3
3
|
import { union, reset, open, fallback, recover } from '../combinator';
|
|
4
|
+
import { MAX_SEGMENT_SIZE } from './segment';
|
|
4
5
|
import { emptyline } from './source';
|
|
5
6
|
import { pagebreak } from './block/pagebreak';
|
|
6
7
|
import { heading } from './block/heading';
|
|
@@ -40,7 +41,8 @@ export import ParagraphParser = BlockParser.ParagraphParser;
|
|
|
40
41
|
export const block: BlockParser = reset(
|
|
41
42
|
{
|
|
42
43
|
resources: {
|
|
43
|
-
|
|
44
|
+
// バックトラックのせいで文字数制限を受けないようにする。
|
|
45
|
+
clock: MAX_SEGMENT_SIZE * 11 + 1,
|
|
44
46
|
recursions: [
|
|
45
47
|
10 || Recursion.block,
|
|
46
48
|
20 || Recursion.blockquote,
|
package/src/parser/header.ts
CHANGED
|
@@ -5,16 +5,15 @@ import { inline } from '../inline';
|
|
|
5
5
|
import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
|
|
6
6
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
7
|
|
|
8
|
-
export const annotation: AnnotationParser = lazy(() => constraint(State.annotation,
|
|
8
|
+
export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, surround(
|
|
9
9
|
'((',
|
|
10
|
-
precedence(1, state(State.annotation
|
|
10
|
+
precedence(1, state(State.annotation,
|
|
11
11
|
trimBlankStart(some(union([inline]), ')', [[')', 1]])))),
|
|
12
12
|
'))',
|
|
13
13
|
false,
|
|
14
14
|
([, ns], rest, context) =>
|
|
15
|
-
context.linebreak ===
|
|
16
|
-
|
|
17
|
-
? [[html('sup', { class: 'annotation' }, [html('span', defrag(ns))])], rest]
|
|
15
|
+
context.linebreak === 0
|
|
16
|
+
? [[html('sup', { class: 'annotation' }, [html('span', defrag(trimBlankNodeEnd(ns)))])], rest]
|
|
18
17
|
: undefined,
|
|
19
18
|
undefined,
|
|
20
19
|
[3 | Backtrack.doublebracket, 1 | Backtrack.bracket])));
|
|
@@ -15,7 +15,7 @@ export const account: AutolinkParser.AccountParser = lazy(() => rewrite(
|
|
|
15
15
|
str(/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*/i),
|
|
16
16
|
])),
|
|
17
17
|
union([
|
|
18
|
-
constraint(State.autolink,
|
|
18
|
+
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
19
19
|
source =>
|
|
20
20
|
`[${source}]{ ${source.includes('/')
|
|
21
21
|
? `https://${source.slice(1).replace('/', '/@')}`
|
|
@@ -18,7 +18,7 @@ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>',
|
|
|
18
18
|
focus(
|
|
19
19
|
/^>>(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?![0-9a-z@#:])/i,
|
|
20
20
|
union([
|
|
21
|
-
constraint(State.autolink,
|
|
21
|
+
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
22
22
|
source =>
|
|
23
23
|
`[${source}]{ ${source.includes('/')
|
|
24
24
|
? `/@${source.slice(2).replace('/', '/timeline?at=')}`
|
|
@@ -9,7 +9,7 @@ import { define } from 'typed-dom/dom';
|
|
|
9
9
|
// https://example/@user?ch=a+b must be a user channel page or a redirect page going there.
|
|
10
10
|
|
|
11
11
|
export const channel: AutolinkParser.ChannelParser = validate('@',
|
|
12
|
-
constraint(State.autolink,
|
|
12
|
+
constraint(State.autolink, bind(
|
|
13
13
|
sequence([
|
|
14
14
|
account,
|
|
15
15
|
some(hashtag),
|
|
@@ -16,7 +16,7 @@ export const email: AutolinkParser.EmailParser = rewrite(
|
|
|
16
16
|
false, undefined, undefined,
|
|
17
17
|
[3 | Backtrack.autolink]),
|
|
18
18
|
union([
|
|
19
|
-
constraint(State.autolink,
|
|
19
|
+
constraint(State.autolink, state(State.autolink,
|
|
20
20
|
({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], ''])),
|
|
21
21
|
({ source }) => [[source], ''],
|
|
22
22
|
]));
|
|
@@ -13,7 +13,7 @@ export const hashnum: AutolinkParser.HashnumParser = lazy(() => rewrite(
|
|
|
13
13
|
/^[0-9]{1,9}(?![^\p{C}\p{S}\p{P}\s]|emoji)/u.source,
|
|
14
14
|
].join('').replace(/emoji/, emoji), 'u'))),
|
|
15
15
|
union([
|
|
16
|
-
constraint(State.autolink,
|
|
16
|
+
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
17
17
|
source => `[${source}]{ ${source.slice(1)} }`,
|
|
18
18
|
unsafelink,
|
|
19
19
|
false),
|
|
@@ -20,7 +20,7 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => rewrite(
|
|
|
20
20
|
false,
|
|
21
21
|
[3 | Backtrack.autolink]),
|
|
22
22
|
union([
|
|
23
|
-
constraint(State.autolink,
|
|
23
|
+
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
24
24
|
source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`,
|
|
25
25
|
unsafelink,
|
|
26
26
|
false),
|
|
@@ -21,6 +21,7 @@ describe('Unit: parser/inline/autolink/url', () => {
|
|
|
21
21
|
|
|
22
22
|
it('basic', () => {
|
|
23
23
|
assert.deepStrictEqual(inspect(parser('http://a')), [['<a class="url" href="http://a" target="_blank">http://a</a>'], '']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('http://a/')), [['<a class="url" href="http://a/" target="_blank">http://a/</a>'], '']);
|
|
24
25
|
assert.deepStrictEqual(inspect(parser('http://a:80')), [['<a class="url" href="http://a:80" target="_blank">http://a:80</a>'], '']);
|
|
25
26
|
assert.deepStrictEqual(inspect(parser('http://a.b')), [['<a class="url" href="http://a.b" target="_blank">http://a.b</a>'], '']);
|
|
26
27
|
assert.deepStrictEqual(inspect(parser(`http://a?#${encodeURIComponent(':/[]()<>?#=& ')}`)), [['<a class="url" href="http://a?#%3A%2F%5B%5D()%3C%3E%3F%23%3D%26%20" target="_blank">http://a?#%3A%2F[]()<>%3F%23%3D%26%20</a>'], '']);
|
|
@@ -55,13 +56,18 @@ describe('Unit: parser/inline/autolink/url', () => {
|
|
|
55
56
|
assert.deepStrictEqual(inspect(parser('http://host=')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '=']);
|
|
56
57
|
assert.deepStrictEqual(inspect(parser('http://host~')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '~']);
|
|
57
58
|
assert.deepStrictEqual(inspect(parser('http://host^')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '^']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser('http://host_')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '_']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser('http://host/')), [['<a class="url" href="http://host/" target="_blank">http://host/</a>'], '']);
|
|
61
|
+
assert.deepStrictEqual(inspect(parser('http://host//')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '//']);
|
|
58
62
|
assert.deepStrictEqual(inspect(parser(`http://host'`)), [['<a class="url" href="http://host\'" target="_blank">http://host\'</a>'], '']);
|
|
59
63
|
assert.deepStrictEqual(inspect(parser('http://host"')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '"']);
|
|
60
64
|
assert.deepStrictEqual(inspect(parser('http://host`')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '`']);
|
|
61
65
|
assert.deepStrictEqual(inspect(parser('http://host|')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '|']);
|
|
62
66
|
assert.deepStrictEqual(inspect(parser('http://host&')), [['<a class="url" href="http://host&" target="_blank">http://host&</a>'], '']);
|
|
63
|
-
assert.deepStrictEqual(inspect(parser('http://
|
|
64
|
-
assert.deepStrictEqual(inspect(parser('http://host$')), [['<a class="url" href="http://host
|
|
67
|
+
assert.deepStrictEqual(inspect(parser('http://host$')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '$']);
|
|
68
|
+
assert.deepStrictEqual(inspect(parser('http://host#"$"')), [['<a class="url" href="http://host#" target="_blank">http://host#</a>'], '"$"']);
|
|
69
|
+
assert.deepStrictEqual(inspect(parser('http://host#($)')), [['<a class="url" href="http://host#" target="_blank">http://host#</a>'], '($)']);
|
|
70
|
+
assert.deepStrictEqual(inspect(parser('http://host#(($))')), [['<a class="url" href="http://host#" target="_blank">http://host#</a>'], '(($))']);
|
|
65
71
|
assert.deepStrictEqual(inspect(parser('http://user@host')), [['<a class="url" href="http://user@host" target="_blank">http://user@host</a>'], '']);
|
|
66
72
|
assert.deepStrictEqual(inspect(parser('http://host#@')), [['<a class="url" href="http://host#@" target="_blank">http://host#@</a>'], '']);
|
|
67
73
|
assert.deepStrictEqual(inspect(parser('http://host[')), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '[']);
|
|
@@ -4,19 +4,18 @@ import { union, tails, some, recursion, precedence, state, constraint, validate,
|
|
|
4
4
|
import { unsafelink } from '../link';
|
|
5
5
|
import { linebreak, unescsource, str } from '../../source';
|
|
6
6
|
|
|
7
|
-
const closer = /^[-+*=~^_,.;:!?]*(?=[\\"`|\[\](){}<>]|[^\x21-\x7E]|$)/;
|
|
8
|
-
|
|
9
7
|
export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'https://'], rewrite(
|
|
10
8
|
open(
|
|
11
9
|
/^https?:\/\/(?=[\x21-\x7E])/,
|
|
12
10
|
precedence(1, some(union([
|
|
13
11
|
verify(bracket, ns => ns.length > 0),
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// 再帰に注意
|
|
13
|
+
some(unescsource, /^[-+*=~^_/,.;:!?]{2}|^[-+*=~^_,.;:!?]?(?=[\\"`|\[\](){}<>]|[^\x21-\x7E]|$)/),
|
|
14
|
+
]), undefined, [[/^[^\x21-\x7E]|^\$/, 9]])),
|
|
16
15
|
false,
|
|
17
16
|
[3 | Backtrack.autolink]),
|
|
18
17
|
union([
|
|
19
|
-
constraint(State.autolink,
|
|
18
|
+
constraint(State.autolink, state(State.autolink, convert(
|
|
20
19
|
url => `{ ${url} }`,
|
|
21
20
|
unsafelink,
|
|
22
21
|
false))),
|
|
@@ -30,7 +29,7 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => open(
|
|
|
30
29
|
tails([
|
|
31
30
|
str('!'),
|
|
32
31
|
union([
|
|
33
|
-
constraint(State.autolink,
|
|
32
|
+
constraint(State.autolink, state(State.autolink, convert(
|
|
34
33
|
url => `{ ${url} }`,
|
|
35
34
|
unsafelink,
|
|
36
35
|
false))),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BracketParser } from '../inline';
|
|
2
|
-
import { Recursion, Backtrack } from '../context';
|
|
3
|
-
import { union, some, recursion, precedence, surround, lazy } from '../../combinator';
|
|
2
|
+
import { State, Recursion, Backtrack } from '../context';
|
|
3
|
+
import { union, some, recursion, precedence, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
|
+
import { textlink } from './link';
|
|
5
6
|
import { str } from '../source';
|
|
6
7
|
import { unshift, push } from 'spica/array';
|
|
7
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
@@ -41,7 +42,28 @@ export const bracket: BracketParser = lazy(() => union([
|
|
|
41
42
|
precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
|
|
42
43
|
str(']'),
|
|
43
44
|
true,
|
|
44
|
-
|
|
45
|
+
([as, bs = [], cs], rest, context) => {
|
|
46
|
+
if (context.state! & State.link) {
|
|
47
|
+
const { recent } = context;
|
|
48
|
+
const head = recent!.reduce((a, b) => a + b.length, rest.length);
|
|
49
|
+
if (context.linebreak! > 0 || rest[0] !== '{') {
|
|
50
|
+
setBacktrack(context, [2 | Backtrack.link], head);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
context.state! ^= State.link;
|
|
54
|
+
assert(rest.length > 0);
|
|
55
|
+
const result = !isBacktrack(context, [1 | Backtrack.link], rest)
|
|
56
|
+
? textlink({ source: rest, context })
|
|
57
|
+
: undefined;
|
|
58
|
+
if (!result) {
|
|
59
|
+
setBacktrack(context, [2 | Backtrack.link], head);
|
|
60
|
+
}
|
|
61
|
+
context.state! ^= State.link;
|
|
62
|
+
context.recent = recent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [push(unshift(as, bs), cs), rest];
|
|
66
|
+
},
|
|
45
67
|
([as, bs = []], rest) => [unshift(as, bs), rest],
|
|
46
68
|
[2 | Backtrack.bracket]),
|
|
47
69
|
surround(
|