securemark 0.294.6 → 0.294.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/index.js +226 -216
- package/markdown.d.ts +13 -36
- package/package.json +1 -1
- package/src/combinator/control/constraint/block.ts +2 -2
- package/src/combinator/control/constraint/line.ts +7 -5
- package/src/combinator/control/manipulation/convert.ts +2 -1
- package/src/combinator/control/manipulation/fence.ts +4 -4
- package/src/combinator/control/manipulation/indent.ts +3 -5
- package/src/combinator/control/manipulation/surround.ts +47 -18
- package/src/combinator/data/parser/some.ts +1 -1
- package/src/combinator/data/parser/union.ts +6 -2
- package/src/parser/api/bind.test.ts +0 -1
- package/src/parser/api/normalize.test.ts +5 -8
- package/src/parser/api/normalize.ts +11 -11
- package/src/parser/api/parse.test.ts +3 -3
- package/src/parser/autolink.ts +1 -2
- package/src/parser/block/extension/fig.ts +4 -1
- package/src/parser/block/heading.ts +12 -2
- package/src/parser/block/reply/quote.ts +1 -2
- package/src/parser/block/ulist.ts +1 -1
- package/src/parser/block.ts +0 -4
- package/src/parser/header.ts +28 -40
- package/src/parser/inline/annotation.ts +2 -3
- package/src/parser/inline/autolink/account.ts +47 -17
- package/src/parser/inline/autolink/anchor.test.ts +0 -1
- package/src/parser/inline/autolink/anchor.ts +15 -15
- package/src/parser/inline/autolink/email.test.ts +1 -1
- package/src/parser/inline/autolink/email.ts +10 -11
- package/src/parser/inline/autolink/hashnum.ts +17 -14
- package/src/parser/inline/autolink/hashtag.ts +23 -19
- package/src/parser/inline/autolink/url.ts +24 -19
- package/src/parser/inline/autolink.ts +36 -25
- package/src/parser/inline/bracket.ts +14 -14
- package/src/parser/inline/deletion.ts +2 -1
- package/src/parser/inline/emphasis.ts +2 -1
- package/src/parser/inline/emstrong.ts +2 -1
- package/src/parser/inline/extension/index.ts +4 -4
- package/src/parser/inline/extension/indexer.ts +1 -1
- package/src/parser/inline/extension/label.ts +1 -1
- package/src/parser/inline/extension/placeholder.ts +4 -3
- package/src/parser/inline/html.ts +5 -5
- package/src/parser/inline/htmlentity.ts +3 -3
- package/src/parser/inline/insertion.ts +2 -1
- package/src/parser/inline/italic.ts +2 -1
- package/src/parser/inline/link.ts +7 -20
- package/src/parser/inline/mark.ts +2 -1
- package/src/parser/inline/math.ts +4 -2
- package/src/parser/inline/media.ts +24 -25
- package/src/parser/inline/reference.ts +4 -4
- package/src/parser/inline/remark.ts +2 -1
- package/src/parser/inline/ruby.ts +3 -4
- package/src/parser/inline/strong.ts +2 -1
- package/src/parser/inline/template.ts +10 -10
- package/src/parser/inline.ts +2 -1
- package/src/parser/segment.ts +2 -2
- package/src/parser/source/escapable.ts +3 -4
- package/src/parser/source/line.ts +3 -1
- package/src/parser/source/text.ts +8 -13
- package/src/parser/source/unescapable.ts +2 -4
- package/src/parser/source.ts +1 -2
- package/src/parser/inline/autolink/channel.ts +0 -44
package/markdown.d.ts
CHANGED
|
@@ -614,7 +614,6 @@ export namespace MarkdownParser {
|
|
|
614
614
|
Parser<HTMLSpanElement | HTMLBRElement, Context, [
|
|
615
615
|
InlineParser.MathParser,
|
|
616
616
|
InlineParser.AutolinkParser,
|
|
617
|
-
SourceParser.LinebreakParser,
|
|
618
617
|
SourceParser.UnescapableSourceParser,
|
|
619
618
|
]> {
|
|
620
619
|
}
|
|
@@ -808,13 +807,6 @@ export namespace MarkdownParser {
|
|
|
808
807
|
LinkParser.ParameterParser,
|
|
809
808
|
]> {
|
|
810
809
|
}
|
|
811
|
-
export interface UnsafeLinkParser extends
|
|
812
|
-
Inline<'link/unsafelink'>,
|
|
813
|
-
Parser<HTMLAnchorElement, Context, [
|
|
814
|
-
LinkParser.TextParser,
|
|
815
|
-
LinkParser.ParameterParser,
|
|
816
|
-
]> {
|
|
817
|
-
}
|
|
818
810
|
export interface ContentParser extends
|
|
819
811
|
Inline<'link/content'>,
|
|
820
812
|
Parser<List<Data<string | HTMLElement>>, Context, [
|
|
@@ -1098,7 +1090,6 @@ export namespace MarkdownParser {
|
|
|
1098
1090
|
AutolinkParser.UrlParser.LineUrlParser,
|
|
1099
1091
|
AutolinkParser.UrlParser,
|
|
1100
1092
|
AutolinkParser.EmailParser,
|
|
1101
|
-
AutolinkParser.ChannelParser,
|
|
1102
1093
|
AutolinkParser.AccountParser,
|
|
1103
1094
|
AutolinkParser.HashtagParser,
|
|
1104
1095
|
AutolinkParser.HashnumParser,
|
|
@@ -1110,7 +1101,7 @@ export namespace MarkdownParser {
|
|
|
1110
1101
|
// https://host
|
|
1111
1102
|
Inline<'url'>,
|
|
1112
1103
|
Parser<string | HTMLElement, Context, [
|
|
1113
|
-
|
|
1104
|
+
Parser<HTMLAnchorElement, Context, []>,
|
|
1114
1105
|
InlineParser,
|
|
1115
1106
|
]> {
|
|
1116
1107
|
}
|
|
@@ -1120,7 +1111,7 @@ export namespace MarkdownParser {
|
|
|
1120
1111
|
Parser<string | HTMLElement, Context, [
|
|
1121
1112
|
SourceParser.StrParser,
|
|
1122
1113
|
Parser<string | HTMLElement, Context, [
|
|
1123
|
-
|
|
1114
|
+
Parser<HTMLAnchorElement, Context, []>,
|
|
1124
1115
|
InlineParser,
|
|
1125
1116
|
]>,
|
|
1126
1117
|
]> {
|
|
@@ -1147,43 +1138,37 @@ export namespace MarkdownParser {
|
|
|
1147
1138
|
export interface EmailParser extends
|
|
1148
1139
|
// user@host
|
|
1149
1140
|
Inline<'email'>,
|
|
1150
|
-
Parser<
|
|
1151
|
-
|
|
1152
|
-
]> {
|
|
1153
|
-
}
|
|
1154
|
-
export interface ChannelParser extends
|
|
1155
|
-
// @user#tag
|
|
1156
|
-
Inline<'channel'>,
|
|
1157
|
-
Parser<string | HTMLAnchorElement, Context, [
|
|
1158
|
-
LinkParser.UnsafeLinkParser,
|
|
1141
|
+
Parser<HTMLAnchorElement, Context, [
|
|
1142
|
+
SourceParser.StrParser,
|
|
1159
1143
|
]> {
|
|
1160
1144
|
}
|
|
1161
1145
|
export interface AccountParser extends
|
|
1162
1146
|
// @user
|
|
1147
|
+
// @user#tag
|
|
1163
1148
|
Inline<'account'>,
|
|
1164
|
-
Parser<
|
|
1165
|
-
|
|
1149
|
+
Parser<HTMLAnchorElement, Context, [
|
|
1150
|
+
SourceParser.StrParser,
|
|
1166
1151
|
]> {
|
|
1167
1152
|
}
|
|
1168
1153
|
export interface HashtagParser extends
|
|
1169
1154
|
// #tag
|
|
1170
1155
|
Inline<'hashtag'>,
|
|
1171
|
-
Parser<
|
|
1172
|
-
|
|
1156
|
+
Parser<HTMLAnchorElement, Context, [
|
|
1157
|
+
SourceParser.StrParser,
|
|
1173
1158
|
]> {
|
|
1174
1159
|
}
|
|
1175
1160
|
export interface HashnumParser extends
|
|
1176
1161
|
// #1
|
|
1177
1162
|
Inline<'hashnum'>,
|
|
1178
|
-
Parser<
|
|
1179
|
-
|
|
1163
|
+
Parser<HTMLAnchorElement, Context, [
|
|
1164
|
+
SourceParser.StrParser,
|
|
1180
1165
|
]> {
|
|
1181
1166
|
}
|
|
1182
1167
|
export interface AnchorParser extends
|
|
1183
1168
|
// >>1
|
|
1184
1169
|
Inline<'anchor'>,
|
|
1185
|
-
Parser<
|
|
1186
|
-
|
|
1170
|
+
Parser<HTMLAnchorElement, Context, [
|
|
1171
|
+
SourceParser.StrParser,
|
|
1187
1172
|
]> {
|
|
1188
1173
|
}
|
|
1189
1174
|
}
|
|
@@ -1192,7 +1177,6 @@ export namespace MarkdownParser {
|
|
|
1192
1177
|
Markdown<'autolink'>,
|
|
1193
1178
|
Parser<string | HTMLElement, Context, [
|
|
1194
1179
|
InlineParser.AutolinkParser,
|
|
1195
|
-
SourceParser.LinebreakParser,
|
|
1196
1180
|
SourceParser.UnescapableSourceParser,
|
|
1197
1181
|
]> {
|
|
1198
1182
|
}
|
|
@@ -1210,13 +1194,6 @@ export namespace MarkdownParser {
|
|
|
1210
1194
|
TextParser,
|
|
1211
1195
|
]> {
|
|
1212
1196
|
}
|
|
1213
|
-
export interface LinebreakParser extends
|
|
1214
|
-
// \n
|
|
1215
|
-
Source<'linebreak'>,
|
|
1216
|
-
Parser<HTMLBRElement, Context, [
|
|
1217
|
-
TextParser,
|
|
1218
|
-
]> {
|
|
1219
|
-
}
|
|
1220
1197
|
export interface EscapableSourceParser extends
|
|
1221
1198
|
// abc
|
|
1222
1199
|
Source<'escsource'>,
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser, failsafe } from '../../data/parser';
|
|
2
|
-
import {
|
|
2
|
+
import { isBlankline } from './line';
|
|
3
3
|
|
|
4
4
|
export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
|
|
5
5
|
export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
|
|
@@ -10,7 +10,7 @@ export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
|
|
|
10
10
|
if (position === source.length) return;
|
|
11
11
|
const result = parser(input);
|
|
12
12
|
if (result === undefined) return;
|
|
13
|
-
if (separation && !
|
|
13
|
+
if (separation && !isBlankline(source, context.position)) return;
|
|
14
14
|
assert(context.position === source.length || source[context.position - 1] === '\n');
|
|
15
15
|
return context.position === source.length || source[context.position - 1] === '\n'
|
|
16
16
|
? result
|
|
@@ -16,7 +16,7 @@ export function line<N>(parser: Parser<N>): Parser<N> {
|
|
|
16
16
|
context.source = source;
|
|
17
17
|
context.offset -= position;
|
|
18
18
|
if (result === undefined) return;
|
|
19
|
-
if (
|
|
19
|
+
if (context.position < position + line.length && !isBlankline(source, context.position)) return;
|
|
20
20
|
context.position = position + line.length;
|
|
21
21
|
return result;
|
|
22
22
|
});
|
|
@@ -29,8 +29,10 @@ export function firstline(source: string, position: number): string {
|
|
|
29
29
|
: source.slice(position, i + 1);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const blankline = /[^\S\n]*(?:$|\n)/y;
|
|
33
|
+
export function isBlankline(source: string, position: number): boolean {
|
|
34
|
+
blankline.lastIndex = position;
|
|
35
|
+
return source.length === position
|
|
36
|
+
|| source[position] === '\n'
|
|
37
|
+
|| blankline.test(source);
|
|
36
38
|
}
|
|
@@ -8,6 +8,7 @@ export function convert<N>(conv: (source: string, context: Ctx) => string, parse
|
|
|
8
8
|
const { source, position } = context;
|
|
9
9
|
if (position === source.length) return;
|
|
10
10
|
const src = conv(source.slice(position), context);
|
|
11
|
+
assert(context.position === position);
|
|
11
12
|
if (src === '') {
|
|
12
13
|
if (!empty) return;
|
|
13
14
|
context.position = source.length;
|
|
@@ -22,7 +23,7 @@ export function convert<N>(conv: (source: string, context: Ctx) => string, parse
|
|
|
22
23
|
return result;
|
|
23
24
|
}
|
|
24
25
|
else {
|
|
25
|
-
|
|
26
|
+
const { offset, backtracks } = context;
|
|
26
27
|
const result = parser(subinput(src, context));
|
|
27
28
|
context.position = context.source.length
|
|
28
29
|
assert(context.offset === offset);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Parser, List, Data, Ctx, failsafe } from '../../data/parser';
|
|
2
2
|
import { consume } from '../../../combinator';
|
|
3
|
-
import { firstline,
|
|
3
|
+
import { firstline, isBlankline } from '../constraint/line';
|
|
4
4
|
import { push } from 'spica/array';
|
|
5
5
|
|
|
6
6
|
export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
|
|
@@ -20,20 +20,20 @@ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: Reg
|
|
|
20
20
|
context.position += matches[0].length;
|
|
21
21
|
// Prevent annoying parsing in editing.
|
|
22
22
|
const secondline = firstline(source, context.position);
|
|
23
|
-
if (
|
|
23
|
+
if (isBlankline(secondline, 0) && firstline(source, context.position + secondline.length).trimEnd() !== delim) return;
|
|
24
24
|
let block = '';
|
|
25
25
|
let closer = '';
|
|
26
26
|
let overflow = '';
|
|
27
27
|
for (let count = 1; ; ++count) {
|
|
28
28
|
if (context.position === source.length) break;
|
|
29
29
|
const line = firstline(source, context.position);
|
|
30
|
-
if ((closer || count > limit + 1) &&
|
|
30
|
+
if ((closer || count > limit + 1) && isBlankline(line, 0)) break;
|
|
31
31
|
if(closer) {
|
|
32
32
|
overflow += line;
|
|
33
33
|
}
|
|
34
34
|
if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
|
|
35
35
|
closer = line;
|
|
36
|
-
if (
|
|
36
|
+
if (isBlankline(source, context.position + line.length)) {
|
|
37
37
|
context.position += line.length;
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
@@ -16,7 +16,7 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
|
|
|
16
16
|
opener = / {1,4}|\t{1,2}/y;
|
|
17
17
|
}
|
|
18
18
|
assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^'));
|
|
19
|
-
assert(parser);
|
|
19
|
+
assert(parser = parser as Parser<N>);
|
|
20
20
|
return failsafe(bind(block(match(
|
|
21
21
|
opener,
|
|
22
22
|
memoize(
|
|
@@ -27,10 +27,8 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
|
|
|
27
27
|
return new List([new Data(source.slice(position))]);
|
|
28
28
|
}))),
|
|
29
29
|
([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation),
|
|
30
|
-
(lines, context) =>
|
|
31
|
-
|
|
32
|
-
return parser(subinput(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context));
|
|
33
|
-
}));
|
|
30
|
+
(lines, context) =>
|
|
31
|
+
parser(subinput(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context))));
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
function trimBlockEnd(block: string): string {
|
|
@@ -4,48 +4,70 @@ import { matcher, clear } from '../../../combinator';
|
|
|
4
4
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
5
5
|
opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>,
|
|
6
6
|
optional?: false,
|
|
7
|
+
backtracks?: readonly number[],
|
|
7
8
|
f?: (rss: [List<Data<S>>, List<Data<SubNode<P>>>, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
8
9
|
g?: (rss: [List<Data<S>>, List<Data<SubNode<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
9
|
-
backtracks?: readonly number[],
|
|
10
10
|
): P;
|
|
11
11
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
12
12
|
opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>,
|
|
13
13
|
optional?: boolean,
|
|
14
|
+
backtracks?: readonly number[],
|
|
14
15
|
f?: (rss: [List<Data<S>>, List<Data<SubNode<P>>> | undefined, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
15
16
|
g?: (rss: [List<Data<S>>, List<Data<SubNode<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
16
|
-
backtracks?: readonly number[],
|
|
17
17
|
): P;
|
|
18
18
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
19
19
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
20
20
|
optional?: false,
|
|
21
|
+
backtracks?: readonly number[],
|
|
21
22
|
f?: (rss: [List<Data<S>>, List<Data<Node<P>>>, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
22
23
|
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
23
|
-
backtracks?: readonly number[],
|
|
24
24
|
): P;
|
|
25
25
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
26
26
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
27
27
|
optional?: boolean,
|
|
28
|
+
backtracks?: readonly number[],
|
|
28
29
|
f?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
29
30
|
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
31
|
+
): P;
|
|
32
|
+
export function surround<P extends Parser<string>, S = string>(
|
|
33
|
+
opener: string | RegExp | Parser<S, Context<P>>, parser: string | RegExp | P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
34
|
+
optional?: false,
|
|
30
35
|
backtracks?: readonly number[],
|
|
36
|
+
f?: (rss: [List<Data<S>>, List<Data<Node<P>>>, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
37
|
+
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
38
|
+
): P;
|
|
39
|
+
export function surround<P extends Parser<string>, S = string>(
|
|
40
|
+
opener: string | RegExp | Parser<S, Context<P>>, parser: string | RegExp | P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
41
|
+
optional?: boolean,
|
|
42
|
+
backtracks?: readonly number[],
|
|
43
|
+
f?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
44
|
+
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
31
45
|
): P;
|
|
32
46
|
export function surround<N>(
|
|
33
|
-
opener: string | RegExp | Parser<N>, parser: Parser<N>, closer: string | RegExp | Parser<N>,
|
|
47
|
+
opener: string | RegExp | Parser<N>, parser: string | RegExp | Parser<N>, closer: string | RegExp | Parser<N>,
|
|
34
48
|
optional: boolean = false,
|
|
49
|
+
backtracks: readonly number[] = [],
|
|
35
50
|
f?: (rss: [List<Data<N>>, List<Data<N>>, List<Data<N>>], context: Ctx) => Result<N>,
|
|
36
51
|
g?: (rss: [List<Data<N>>, List<Data<N>> | undefined], context: Ctx) => Result<N>,
|
|
37
|
-
backtracks: readonly number[] = [],
|
|
38
52
|
): Parser<N> {
|
|
39
53
|
switch (typeof opener) {
|
|
40
54
|
case 'string':
|
|
41
55
|
case 'object':
|
|
42
56
|
opener = clear(matcher(opener, true));
|
|
43
57
|
}
|
|
58
|
+
assert(opener);
|
|
59
|
+
switch (typeof parser) {
|
|
60
|
+
case 'string':
|
|
61
|
+
case 'object':
|
|
62
|
+
parser = clear(matcher(parser, true));
|
|
63
|
+
}
|
|
64
|
+
assert(parser);
|
|
44
65
|
switch (typeof closer) {
|
|
45
66
|
case 'string':
|
|
46
67
|
case 'object':
|
|
47
68
|
closer = clear(matcher(closer, true));
|
|
48
69
|
}
|
|
70
|
+
assert(closer);
|
|
49
71
|
return failsafe(input => {
|
|
50
72
|
const { context } = input;
|
|
51
73
|
const { source, position } = context;
|
|
@@ -66,8 +88,7 @@ export function surround<N>(
|
|
|
66
88
|
if (!nodesM && !optional) {
|
|
67
89
|
setBacktrack(context, backtracks, position);
|
|
68
90
|
const result = g?.([nodesO, nodesM], context);
|
|
69
|
-
revert(context, linebreak);
|
|
70
|
-
return result;
|
|
91
|
+
return result || void revert(context, linebreak);
|
|
71
92
|
}
|
|
72
93
|
const nodesC = nodesM || optional ? closer(input) : undefined;
|
|
73
94
|
assert(context.position >= position);
|
|
@@ -75,8 +96,7 @@ export function surround<N>(
|
|
|
75
96
|
if (!nodesC) {
|
|
76
97
|
setBacktrack(context, backtracks, position);
|
|
77
98
|
const result = g?.([nodesO, nodesM], context);
|
|
78
|
-
revert(context, linebreak);
|
|
79
|
-
return result;
|
|
99
|
+
return result || void revert(context, linebreak);
|
|
80
100
|
}
|
|
81
101
|
if (context.position === position) {
|
|
82
102
|
return void revert(context, linebreak);
|
|
@@ -88,10 +108,7 @@ export function surround<N>(
|
|
|
88
108
|
if (result) {
|
|
89
109
|
context.linebreak ||= linebreak;
|
|
90
110
|
}
|
|
91
|
-
|
|
92
|
-
revert(context, linebreak);
|
|
93
|
-
}
|
|
94
|
-
return result;
|
|
111
|
+
return result || void revert(context, linebreak);
|
|
95
112
|
});
|
|
96
113
|
}
|
|
97
114
|
export function open<P extends Parser<unknown>>(
|
|
@@ -100,13 +117,19 @@ export function open<P extends Parser<unknown>>(
|
|
|
100
117
|
optional?: boolean,
|
|
101
118
|
backtracks?: readonly number[],
|
|
102
119
|
): P;
|
|
120
|
+
export function open<P extends Parser<string>>(
|
|
121
|
+
opener: string | RegExp | Parser<Node<P>, Context<P>>,
|
|
122
|
+
parser: string | RegExp | P,
|
|
123
|
+
optional?: boolean,
|
|
124
|
+
backtracks?: readonly number[],
|
|
125
|
+
): P;
|
|
103
126
|
export function open<N>(
|
|
104
127
|
opener: string | RegExp | Parser<N, Ctx>,
|
|
105
|
-
parser: Parser<N>,
|
|
128
|
+
parser: string | RegExp | Parser<N>,
|
|
106
129
|
optional?: boolean,
|
|
107
130
|
backtracks?: readonly number[],
|
|
108
131
|
): Parser<N> {
|
|
109
|
-
return surround(opener, parser
|
|
132
|
+
return surround(opener, parser as Parser<N>, '', optional, backtracks);
|
|
110
133
|
}
|
|
111
134
|
export function close<P extends Parser<unknown>>(
|
|
112
135
|
parser: P,
|
|
@@ -114,13 +137,19 @@ export function close<P extends Parser<unknown>>(
|
|
|
114
137
|
optional?: boolean,
|
|
115
138
|
backtracks?: readonly number[],
|
|
116
139
|
): P;
|
|
140
|
+
export function close<P extends Parser<string>>(
|
|
141
|
+
parser: string | RegExp | P,
|
|
142
|
+
closer: string | RegExp | Parser<Node<P>, Context<P>>,
|
|
143
|
+
optional?: boolean,
|
|
144
|
+
backtracks?: readonly number[],
|
|
145
|
+
): P;
|
|
117
146
|
export function close<N>(
|
|
118
|
-
parser: Parser<N>,
|
|
147
|
+
parser: string | RegExp | Parser<N>,
|
|
119
148
|
closer: string | RegExp | Parser<N, Ctx>,
|
|
120
149
|
optional?: boolean,
|
|
121
150
|
backtracks?: readonly number[],
|
|
122
151
|
): Parser<N> {
|
|
123
|
-
return surround('', parser
|
|
152
|
+
return surround('', parser as Parser<N>, closer, optional, backtracks);
|
|
124
153
|
}
|
|
125
154
|
|
|
126
155
|
const statesize = 2;
|
|
@@ -153,7 +182,7 @@ export function setBacktrack(
|
|
|
153
182
|
position: number,
|
|
154
183
|
length: number = 1,
|
|
155
184
|
): void {
|
|
156
|
-
//
|
|
185
|
+
// バックトラックの可能性がなく記録不要の場合もあるが判別が面倒なので省略
|
|
157
186
|
const { source } = context;
|
|
158
187
|
if (position === source.length) return;
|
|
159
188
|
if (length === 0) return;
|
|
@@ -24,7 +24,7 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
|
|
|
24
24
|
context.delimiters.push(delims);
|
|
25
25
|
}
|
|
26
26
|
// whileは数倍遅い
|
|
27
|
-
for (; context.position <
|
|
27
|
+
for (const len = source.length; context.position < len;) {
|
|
28
28
|
if (match(input)) break;
|
|
29
29
|
if (context.delimiters?.match(input)) break;
|
|
30
30
|
const result = parser(input);
|
|
@@ -10,8 +10,12 @@ export function union<N, D extends Parser<N>[]>(parsers: D): Parser<N, Ctx, D> {
|
|
|
10
10
|
return parsers[0];
|
|
11
11
|
default:
|
|
12
12
|
return eval([
|
|
13
|
+
'((',
|
|
14
|
+
parsers.map((_, i) => `parser${i},`).join(''),
|
|
15
|
+
') =>',
|
|
13
16
|
'input =>',
|
|
14
|
-
parsers.map((_, i) => `||
|
|
15
|
-
|
|
17
|
+
parsers.map((_, i) => `|| parser${i}(input)`).join('').slice(2),
|
|
18
|
+
')',
|
|
19
|
+
].join(''))(...parsers);
|
|
16
20
|
}
|
|
17
21
|
}
|
|
@@ -48,7 +48,6 @@ describe('Unit: parser/api/bind', () => {
|
|
|
48
48
|
[
|
|
49
49
|
'<h1 class="error">Error: Too large segment over 100,000 bytes.</h1>',
|
|
50
50
|
`<pre class="error" translate="no">${'\n'.repeat(997)}...</pre>`,
|
|
51
|
-
'<h1 class="error">Error: Too large segment over 100,000 bytes.</h1>',
|
|
52
51
|
]);
|
|
53
52
|
});
|
|
54
53
|
|
|
@@ -2,10 +2,6 @@ import { normalize, escape } from './normalize';
|
|
|
2
2
|
|
|
3
3
|
describe('Unit: parser/normalize', () => {
|
|
4
4
|
describe('normalize', () => {
|
|
5
|
-
it('invalid surrogate pairs', () => {
|
|
6
|
-
assert(normalize('\uDC00\uD800') === '\uFFFD\uFFFD');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
5
|
it('controls', () => {
|
|
10
6
|
assert(normalize('\r') === '\n');
|
|
11
7
|
assert(normalize('\r\n') === '\n');
|
|
@@ -50,10 +46,11 @@ describe('Unit: parser/normalize', () => {
|
|
|
50
46
|
assert(normalize('\u202A') === '\uFFFD');
|
|
51
47
|
assert(normalize('\u202F') === '\uFFFD');
|
|
52
48
|
assert(normalize('\uFEFF') === '\uFFFD');
|
|
53
|
-
assert(normalize('\u180E') === '\uFFFD');
|
|
54
|
-
assert(normalize('\u1820\u180E') === '\u1820\u180E');
|
|
55
|
-
assert(normalize('\u1821\u180E') === '\u1821\u180E');
|
|
56
|
-
assert(normalize('\u1822\u180E') === '\u1822\uFFFD');
|
|
49
|
+
//assert(normalize('\u180E') === '\uFFFD');
|
|
50
|
+
//assert(normalize('\u1820\u180E') === '\u1820\u180E');
|
|
51
|
+
//assert(normalize('\u1821\u180E') === '\u1821\u180E');
|
|
52
|
+
//assert(normalize('\u1822\u180E') === '\u1822\uFFFD');
|
|
53
|
+
//assert(normalize('\uDC00\uD800') === '\uFFFD\uFFFD');
|
|
57
54
|
});
|
|
58
55
|
|
|
59
56
|
it('header', () => {
|
|
@@ -9,17 +9,18 @@ export function normalize(source: string): string {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function format(source: string): string {
|
|
12
|
-
return source
|
|
13
|
-
.replace(/\r\n?/g, '\n');
|
|
12
|
+
return source.replace(/\r\n?/g, '\n');
|
|
14
13
|
}
|
|
15
14
|
|
|
15
|
+
const invalid = new RegExp([
|
|
16
|
+
/(?![\t\r\n])[\x00-\x1F\x7F]/g.source,
|
|
17
|
+
/(?!\u200D)[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]/g.source,
|
|
18
|
+
// 後読みが重い
|
|
19
|
+
///(?<![\u1820\u1821])\u180E/g.source,
|
|
20
|
+
///[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g.source,
|
|
21
|
+
].join('|'), 'g');
|
|
16
22
|
function sanitize(source: string): string {
|
|
17
|
-
return source
|
|
18
|
-
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|(?!\u200D)[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(?<![\u1820\u1821])\u180E/g, UNICODE_REPLACEMENT_CHARACTER)
|
|
19
|
-
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/g, char =>
|
|
20
|
-
char.length === 1
|
|
21
|
-
? UNICODE_REPLACEMENT_CHARACTER
|
|
22
|
-
: char);
|
|
23
|
+
return source.replace(invalid, UNICODE_REPLACEMENT_CHARACTER);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// https://dev.w3.org/html5/html-author/charref
|
|
@@ -110,7 +111,6 @@ assert(unreadableSpecialCharacters.every(c => sanitize(c) === UNICODE_REPLACEMEN
|
|
|
110
111
|
|
|
111
112
|
// 特殊不可視文字はエディタおよびソースビューアでは等幅および強調表示により可視化する
|
|
112
113
|
export function escape(source: string): string {
|
|
113
|
-
return source
|
|
114
|
-
.
|
|
115
|
-
`&${unreadableEscapeHTMLEntityNames[unreadableEscapeCharacters.indexOf(char)]};`);
|
|
114
|
+
return source.replace(unreadableEscapeCharacter, char =>
|
|
115
|
+
`&${unreadableEscapeHTMLEntityNames[unreadableEscapeCharacters.indexOf(char)]};`);
|
|
116
116
|
}
|
|
@@ -361,9 +361,9 @@ describe('Unit: parser/api/parse', () => {
|
|
|
361
361
|
|
|
362
362
|
it('backtrack', function () {
|
|
363
363
|
this.timeout(5000);
|
|
364
|
-
// 最悪計算量での実行速度はCommonMarkの公式JS実装の32n
|
|
364
|
+
// 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して3倍遅い程度。
|
|
365
365
|
// 5n = annotation/reference + link + url/math + ruby + text
|
|
366
|
-
const source = `((([[[[#$[${'.'.repeat(
|
|
366
|
+
const source = `((([[[[#$[${'.'.repeat(19998)}`;
|
|
367
367
|
assert.deepStrictEqual(
|
|
368
368
|
[...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
|
|
369
369
|
.map(el => el.tagName),
|
|
@@ -372,7 +372,7 @@ describe('Unit: parser/api/parse', () => {
|
|
|
372
372
|
|
|
373
373
|
it('backtrack error', function () {
|
|
374
374
|
this.timeout(5000);
|
|
375
|
-
const source = `((([[[[#$[${'.'.repeat(
|
|
375
|
+
const source = `((([[[[#$[${'.'.repeat(19998 + 1)}`;
|
|
376
376
|
assert.deepStrictEqual(
|
|
377
377
|
[...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
|
|
378
378
|
.map(el => el.tagName),
|
package/src/parser/autolink.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { MarkdownParser } from '../../markdown';
|
|
2
2
|
import { union, some, lazy } from '../combinator';
|
|
3
3
|
import { autolink as autolink_ } from './inline/autolink';
|
|
4
|
-
import {
|
|
4
|
+
import { unescsource } from './source';
|
|
5
5
|
|
|
6
6
|
export import AutolinkParser = MarkdownParser.AutolinkParser;
|
|
7
7
|
|
|
8
8
|
export const autolink: AutolinkParser = lazy(() =>
|
|
9
9
|
some(union([
|
|
10
10
|
autolink_,
|
|
11
|
-
linebreak,
|
|
12
11
|
unescsource,
|
|
13
12
|
])));
|
|
@@ -30,7 +30,10 @@ export const fig: FigParser = block(rewrite(segment, verify(convert(
|
|
|
30
30
|
// Bug: TypeScript
|
|
31
31
|
const fence = (/^[^\n]*\n!?>+ /.test(source) && source.match(/^~{3,}(?=[^\S\n]*$)/mg) as string[] || [])
|
|
32
32
|
.reduce((max, fence) => fence > max ? fence : max, '~~') + '~';
|
|
33
|
-
|
|
33
|
+
const { position } = context;
|
|
34
|
+
const result = parser({ context });
|
|
35
|
+
context.position = position;
|
|
36
|
+
return result
|
|
34
37
|
? `${fence}figure ${source.replace(/^(.+\n.+\n)([\S\s]+?)\n?$/, '$1\n$2')}\n${fence}`
|
|
35
38
|
: `${fence}figure ${source}\n\n${fence}`;
|
|
36
39
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HeadingParser } from '../block';
|
|
2
2
|
import { State } from '../context';
|
|
3
3
|
import { List, Data } from '../../combinator/data/parser';
|
|
4
|
-
import { union, some, state, block, line, focus, rewrite, open, fmap } from '../../combinator';
|
|
4
|
+
import { union, some, state, block, line, focus, rewrite, open, fmap, firstline } from '../../combinator';
|
|
5
5
|
import { inline, indexee, indexer, dataindex } from '../inline';
|
|
6
6
|
import { str } from '../source';
|
|
7
7
|
import { visualize, trimBlank } from '../visibility';
|
|
@@ -10,7 +10,17 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
10
10
|
|
|
11
11
|
export const segment: HeadingParser.SegmentParser = block(focus(
|
|
12
12
|
/#+ +\S[^\n]*(?:\n#+(?=$|[ \n])[^\n]*)*(?:$|\n)/y,
|
|
13
|
-
|
|
13
|
+
input => {
|
|
14
|
+
const { context } = input;
|
|
15
|
+
const { source } = context;
|
|
16
|
+
const acc = new List<Data<string>>();
|
|
17
|
+
for (; context.position < source.length;) {
|
|
18
|
+
const line = firstline(source, context.position);
|
|
19
|
+
acc.push(new Data(line));
|
|
20
|
+
context.position += line.length;
|
|
21
|
+
}
|
|
22
|
+
return acc;
|
|
23
|
+
}));
|
|
14
24
|
|
|
15
25
|
export const heading: HeadingParser = block(rewrite(segment,
|
|
16
26
|
// その他の表示制御は各所のCSSで行う。
|
|
@@ -3,7 +3,7 @@ import { List, Data } from '../../../combinator/data/parser';
|
|
|
3
3
|
import { union, some, block, validate, rewrite, convert, lazy, fmap } from '../../../combinator';
|
|
4
4
|
import { math } from '../../inline/math';
|
|
5
5
|
import { autolink } from '../../inline/autolink';
|
|
6
|
-
import {
|
|
6
|
+
import { unescsource, anyline } from '../../source';
|
|
7
7
|
import { unwrap } from '../../util';
|
|
8
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
9
|
|
|
@@ -19,7 +19,6 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
|
|
|
19
19
|
// quote補助関数が残した数式をパースする。
|
|
20
20
|
math,
|
|
21
21
|
autolink,
|
|
22
|
-
linebreak,
|
|
23
22
|
unescsource,
|
|
24
23
|
])),
|
|
25
24
|
false)),
|
package/src/parser/block.ts
CHANGED
|
@@ -62,10 +62,6 @@ export const block: BlockParser = reset(
|
|
|
62
62
|
if (position === source.length) return;
|
|
63
63
|
const fst = source[position];
|
|
64
64
|
switch (fst) {
|
|
65
|
-
case '\n':
|
|
66
|
-
assert(source.trim() === '');
|
|
67
|
-
input.context.position = source.length;
|
|
68
|
-
return new List();
|
|
69
65
|
case '=':
|
|
70
66
|
if (source.startsWith('===', position)) return pagebreak(input);
|
|
71
67
|
break;
|