securemark 0.255.1 → 0.257.1
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 +14 -0
- package/dist/index.js +243 -208
- package/markdown.d.ts +39 -17
- package/package.json +1 -1
- package/src/combinator/control/constraint/contract.ts +3 -13
- package/src/combinator/control/manipulation/context.ts +13 -2
- package/src/combinator/control/manipulation/resource.ts +37 -3
- package/src/combinator/control/manipulation/surround.ts +6 -6
- package/src/combinator/data/parser/inits.ts +1 -1
- package/src/combinator/data/parser/sequence.ts +1 -1
- package/src/combinator/data/parser/some.ts +16 -38
- package/src/combinator/data/parser.ts +34 -18
- package/src/debug.test.ts +2 -2
- package/src/parser/api/bind.ts +9 -11
- package/src/parser/api/parse.test.ts +51 -9
- package/src/parser/block.ts +1 -1
- package/src/parser/inline/annotation.test.ts +7 -5
- package/src/parser/inline/annotation.ts +10 -6
- package/src/parser/inline/autolink/account.ts +3 -7
- package/src/parser/inline/autolink/anchor.ts +3 -7
- package/src/parser/inline/autolink/hashnum.ts +3 -7
- package/src/parser/inline/autolink/hashtag.ts +3 -7
- package/src/parser/inline/autolink/url.test.ts +1 -0
- package/src/parser/inline/autolink/url.ts +7 -8
- package/src/parser/inline/bracket.test.ts +11 -7
- package/src/parser/inline/bracket.ts +11 -11
- package/src/parser/inline/comment.test.ts +4 -3
- package/src/parser/inline/comment.ts +4 -4
- package/src/parser/inline/deletion.ts +3 -3
- package/src/parser/inline/emphasis.ts +3 -3
- package/src/parser/inline/emstrong.ts +4 -5
- package/src/parser/inline/extension/index.test.ts +1 -0
- package/src/parser/inline/extension/index.ts +8 -7
- package/src/parser/inline/extension/indexer.ts +3 -5
- package/src/parser/inline/extension/label.ts +1 -1
- package/src/parser/inline/extension/placeholder.test.ts +8 -7
- package/src/parser/inline/extension/placeholder.ts +4 -4
- package/src/parser/inline/html.test.ts +2 -0
- package/src/parser/inline/html.ts +5 -5
- package/src/parser/inline/insertion.ts +3 -3
- package/src/parser/inline/link.test.ts +1 -0
- package/src/parser/inline/link.ts +58 -16
- package/src/parser/inline/mark.ts +3 -3
- package/src/parser/inline/math.test.ts +21 -14
- package/src/parser/inline/math.ts +4 -15
- package/src/parser/inline/media.test.ts +0 -2
- package/src/parser/inline/media.ts +10 -10
- package/src/parser/inline/reference.test.ts +10 -9
- package/src/parser/inline/reference.ts +12 -8
- package/src/parser/inline/ruby.ts +29 -27
- package/src/parser/inline/strong.ts +3 -3
- package/src/parser/inline/template.ts +4 -4
- package/src/parser/inline.test.ts +13 -10
- package/src/parser/inline.ts +1 -0
- package/src/parser/util.ts +34 -18
package/markdown.d.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { Parser, Ctx } from './src/combinator/data/parser';
|
|
2
2
|
import { Dict } from 'spica/dict';
|
|
3
3
|
|
|
4
|
+
/*
|
|
5
|
+
|
|
6
|
+
Operator precedence
|
|
7
|
+
|
|
8
|
+
9: \n, \\\n
|
|
9
|
+
8: `, "
|
|
10
|
+
7: $
|
|
11
|
+
6: (()), [[]]
|
|
12
|
+
5: <tag></tag>
|
|
13
|
+
4: [% %]
|
|
14
|
+
3: (), [], {}
|
|
15
|
+
2: ==, ++, ~~
|
|
16
|
+
1: *, **
|
|
17
|
+
|
|
18
|
+
*/
|
|
19
|
+
|
|
4
20
|
declare abstract class Markdown<T> {
|
|
5
21
|
private parser?: T;
|
|
6
22
|
}
|
|
@@ -671,7 +687,7 @@ export namespace MarkdownParser {
|
|
|
671
687
|
export interface AnnotationParser extends
|
|
672
688
|
// ((abc))
|
|
673
689
|
Inline<'annotation'>,
|
|
674
|
-
Parser<HTMLElement, Context, [
|
|
690
|
+
Parser<HTMLElement | string, Context, [
|
|
675
691
|
InlineParser,
|
|
676
692
|
]> {
|
|
677
693
|
}
|
|
@@ -680,7 +696,7 @@ export namespace MarkdownParser {
|
|
|
680
696
|
// [[^abbr]]
|
|
681
697
|
// [[^abbr| abc]]
|
|
682
698
|
Inline<'reference'>,
|
|
683
|
-
Parser<HTMLElement, Context, [
|
|
699
|
+
Parser<HTMLElement | string, Context, [
|
|
684
700
|
ReferenceParser.AbbrParser,
|
|
685
701
|
InlineParser,
|
|
686
702
|
InlineParser,
|
|
@@ -740,7 +756,6 @@ export namespace MarkdownParser {
|
|
|
740
756
|
MathParser.BracketParser,
|
|
741
757
|
Parser<string, Context, [
|
|
742
758
|
MathParser.BracketParser,
|
|
743
|
-
MathParser.QuoteParser,
|
|
744
759
|
SourceParser.StrParser,
|
|
745
760
|
]>,
|
|
746
761
|
]> {
|
|
@@ -753,14 +768,6 @@ export namespace MarkdownParser {
|
|
|
753
768
|
SourceParser.EscapableSourceParser,
|
|
754
769
|
]> {
|
|
755
770
|
}
|
|
756
|
-
export interface QuoteParser extends
|
|
757
|
-
Inline<'math/quote'>,
|
|
758
|
-
Parser<HTMLElement, Context, [
|
|
759
|
-
QuoteParser,
|
|
760
|
-
BracketParser,
|
|
761
|
-
SourceParser.StrParser,
|
|
762
|
-
]> {
|
|
763
|
-
}
|
|
764
771
|
}
|
|
765
772
|
export interface ExtensionParser extends
|
|
766
773
|
// [#abc]
|
|
@@ -862,11 +869,20 @@ export namespace MarkdownParser {
|
|
|
862
869
|
// { uri }
|
|
863
870
|
// [abc]{uri nofollow}
|
|
864
871
|
Inline<'link'>,
|
|
865
|
-
Parser<
|
|
872
|
+
Parser<HTMLElement | string, Context, [
|
|
866
873
|
LinkParser.ContentParser,
|
|
867
874
|
LinkParser.ParameterParser,
|
|
868
875
|
]> {
|
|
869
876
|
}
|
|
877
|
+
export interface TextLinkParser extends
|
|
878
|
+
// { uri }
|
|
879
|
+
// [abc]{uri nofollow}
|
|
880
|
+
Inline<'textlink'>,
|
|
881
|
+
Parser<HTMLAnchorElement, Context, [
|
|
882
|
+
LinkParser.TextParser,
|
|
883
|
+
LinkParser.ParameterParser,
|
|
884
|
+
]> {
|
|
885
|
+
}
|
|
870
886
|
export namespace LinkParser {
|
|
871
887
|
export interface ContentParser extends
|
|
872
888
|
Inline<'link/content'>,
|
|
@@ -876,6 +892,12 @@ export namespace MarkdownParser {
|
|
|
876
892
|
InlineParser,
|
|
877
893
|
]> {
|
|
878
894
|
}
|
|
895
|
+
export interface TextParser extends
|
|
896
|
+
Inline<'link/text'>,
|
|
897
|
+
Parser<string[], Context, [
|
|
898
|
+
SourceParser.UnescapableSourceParser,
|
|
899
|
+
]> {
|
|
900
|
+
}
|
|
879
901
|
export interface ParameterParser extends
|
|
880
902
|
Inline<'link/parameter'>,
|
|
881
903
|
Parser<string[], Context, [
|
|
@@ -1100,7 +1122,7 @@ export namespace MarkdownParser {
|
|
|
1100
1122
|
// https://host
|
|
1101
1123
|
Inline<'url'>,
|
|
1102
1124
|
Parser<HTMLAnchorElement, Context, [
|
|
1103
|
-
|
|
1125
|
+
TextLinkParser,
|
|
1104
1126
|
]> {
|
|
1105
1127
|
}
|
|
1106
1128
|
export namespace UrlParser {
|
|
@@ -1142,28 +1164,28 @@ export namespace MarkdownParser {
|
|
|
1142
1164
|
// @user
|
|
1143
1165
|
Inline<'account'>,
|
|
1144
1166
|
Parser<HTMLAnchorElement, Context, [
|
|
1145
|
-
|
|
1167
|
+
TextLinkParser,
|
|
1146
1168
|
]> {
|
|
1147
1169
|
}
|
|
1148
1170
|
export interface HashtagParser extends
|
|
1149
1171
|
// #tag
|
|
1150
1172
|
Inline<'hashtag'>,
|
|
1151
1173
|
Parser<HTMLAnchorElement, Context, [
|
|
1152
|
-
|
|
1174
|
+
TextLinkParser,
|
|
1153
1175
|
]> {
|
|
1154
1176
|
}
|
|
1155
1177
|
export interface HashnumParser extends
|
|
1156
1178
|
// #1
|
|
1157
1179
|
Inline<'hashnum'>,
|
|
1158
1180
|
Parser<HTMLAnchorElement, Context, [
|
|
1159
|
-
|
|
1181
|
+
TextLinkParser,
|
|
1160
1182
|
]> {
|
|
1161
1183
|
}
|
|
1162
1184
|
export interface AnchorParser extends
|
|
1163
1185
|
// >>1
|
|
1164
1186
|
Inline<'anchor'>,
|
|
1165
1187
|
Parser<HTMLAnchorElement, Context, [
|
|
1166
|
-
|
|
1188
|
+
TextLinkParser,
|
|
1167
1189
|
]> {
|
|
1168
1190
|
}
|
|
1169
1191
|
}
|
package/package.json
CHANGED
|
@@ -9,11 +9,9 @@ import { Parser, Ctx, Tree, Context, eval, exec, check } from '../../data/parser
|
|
|
9
9
|
|
|
10
10
|
export function validate<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], parser: P): P;
|
|
11
11
|
export function validate<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], has: string, parser: P): P;
|
|
12
|
-
export function validate<
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
if (typeof end === 'function') return validate(patterns, has, '', end);
|
|
16
|
-
if (!isArray(patterns)) return validate([patterns], has, end!, parser!);
|
|
12
|
+
export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has: string | Parser<T>, parser?: Parser<T>): Parser<T> {
|
|
13
|
+
if (typeof has === 'function') return validate(patterns, '', has);
|
|
14
|
+
if (!isArray(patterns)) return validate([patterns], has, parser!);
|
|
17
15
|
assert(patterns.length > 0);
|
|
18
16
|
assert(patterns.every(pattern => pattern instanceof RegExp ? !pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^') : true));
|
|
19
17
|
assert(parser);
|
|
@@ -26,17 +24,9 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
|
|
|
26
24
|
? `|| source.slice(0, ${pattern.length}) === '${pattern}'`
|
|
27
25
|
: `|| /${pattern.source}/${pattern.flags}.test(source)`),
|
|
28
26
|
].join(''))();
|
|
29
|
-
const match2 = (source: string): boolean => {
|
|
30
|
-
if (!has) return true;
|
|
31
|
-
const i = end ? source.indexOf(end, 1) : -1;
|
|
32
|
-
return i !== -1
|
|
33
|
-
? source.slice(0, i).indexOf(has, 1) !== -1
|
|
34
|
-
: source.indexOf(has, 1) !== -1;
|
|
35
|
-
};
|
|
36
27
|
return (source, context) => {
|
|
37
28
|
if (source === '') return;
|
|
38
29
|
if (!match(source)) return;
|
|
39
|
-
if (!match2(source)) return;
|
|
40
30
|
const result = parser!(source, context);
|
|
41
31
|
assert(check(source, result));
|
|
42
32
|
if (!result) return;
|
|
@@ -13,7 +13,7 @@ export function guard<T>(f: (context: Ctx) => boolean, parser: Parser<T>): Parse
|
|
|
13
13
|
: undefined;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function reset<P extends Parser<unknown>>(
|
|
16
|
+
export function reset<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
17
17
|
export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
18
18
|
assert(Object.getPrototypeOf(base) === Object.prototype);
|
|
19
19
|
assert(Object.freeze(base));
|
|
@@ -21,7 +21,7 @@ export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
|
21
21
|
parser(source, inherit(ObjectCreate(context), base));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function context<P extends Parser<unknown>>(
|
|
24
|
+
export function context<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
|
|
25
25
|
export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
|
|
26
26
|
assert(Object.getPrototypeOf(base) === Object.prototype);
|
|
27
27
|
assert(Object.freeze(base));
|
|
@@ -57,3 +57,14 @@ const inherit = template((prop, target, source) => {
|
|
|
57
57
|
return target[prop] = source[prop];
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
+
|
|
61
|
+
export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
|
|
62
|
+
export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
|
|
63
|
+
return (source, context) => {
|
|
64
|
+
const p = context.precedence;
|
|
65
|
+
context.precedence = precedence;
|
|
66
|
+
const result = parser(source, context);
|
|
67
|
+
context.precedence = p;
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -4,11 +4,11 @@ export function creator<P extends Parser<unknown>>(parser: P): P;
|
|
|
4
4
|
export function creator<P extends Parser<unknown>>(cost: number, parser: P): P;
|
|
5
5
|
export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
6
6
|
if (typeof cost === 'function') return creator(1, cost);
|
|
7
|
-
assert(cost
|
|
7
|
+
assert(cost >= 0);
|
|
8
8
|
return (source, context) => {
|
|
9
9
|
const { resources = { budget: 1, recursion: 1 } } = context;
|
|
10
|
-
if (resources.budget <= 0) throw new Error('Too many creations
|
|
11
|
-
if (resources.recursion <= 0) throw new Error('Too much recursion
|
|
10
|
+
if (resources.budget <= 0) throw new Error('Too many creations');
|
|
11
|
+
if (resources.recursion <= 0) throw new Error('Too much recursion');
|
|
12
12
|
--resources.recursion;
|
|
13
13
|
const result = parser!(source, context);
|
|
14
14
|
++resources.recursion;
|
|
@@ -18,3 +18,37 @@ export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>
|
|
|
18
18
|
return result;
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
export function uncreator<P extends Parser<unknown>>(parser: P): P;
|
|
23
|
+
export function uncreator<P extends Parser<unknown>>(cost: number, parser: P): P;
|
|
24
|
+
export function uncreator(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
25
|
+
if (typeof cost === 'function') return uncreator(1, cost);
|
|
26
|
+
assert(cost >= 0);
|
|
27
|
+
return (source, context) => {
|
|
28
|
+
const { resources = { budget: 1, recursion: 1 } } = context;
|
|
29
|
+
if (resources.budget <= 0) throw new Error('Too many creations');
|
|
30
|
+
if (resources.recursion <= 0) throw new Error('Too much recursion');
|
|
31
|
+
++resources.recursion;
|
|
32
|
+
const result = parser!(source, context);
|
|
33
|
+
--resources.recursion;
|
|
34
|
+
if (result) {
|
|
35
|
+
resources.budget += cost;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function recursion<P extends Parser<unknown>>(parser: P): P;
|
|
42
|
+
export function recursion<P extends Parser<unknown>>(cost: number, parser: P): P;
|
|
43
|
+
export function recursion(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
|
|
44
|
+
if (typeof cost === 'function') return recursion(1, cost);
|
|
45
|
+
assert(cost >= 0);
|
|
46
|
+
return (source, context) => {
|
|
47
|
+
const { resources = { recursion: 1 } } = context;
|
|
48
|
+
if (resources.recursion <= 0) throw new Error('Too much recursion');
|
|
49
|
+
--resources.recursion;
|
|
50
|
+
const result = parser!(source, context);
|
|
51
|
+
++resources.recursion;
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -6,27 +6,27 @@ import { unshift, push } from 'spica/array';
|
|
|
6
6
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
7
7
|
opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>, optional?: false,
|
|
8
8
|
f?: (rss: [S[], SubTree<P>[], S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
9
|
-
g?: (rss: [S[], SubTree<P>[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
9
|
+
g?: (rss: [S[], SubTree<P>[], string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
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>>, optional?: boolean,
|
|
13
13
|
f?: (rss: [S[], SubTree<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
14
|
-
g?: (rss: [S[], SubTree<P>[] | undefined], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
14
|
+
g?: (rss: [S[], SubTree<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
15
15
|
): P;
|
|
16
16
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
17
17
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>, optional?: false,
|
|
18
18
|
f?: (rss: [S[], Tree<P>[], S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
19
|
-
g?: (rss: [S[], Tree<P>[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
19
|
+
g?: (rss: [S[], Tree<P>[], string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
20
20
|
): P;
|
|
21
21
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
22
22
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>, optional?: boolean,
|
|
23
23
|
f?: (rss: [S[], Tree<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
24
|
-
g?: (rss: [S[], Tree<P>[] | undefined], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
24
|
+
g?: (rss: [S[], Tree<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
|
|
25
25
|
): P;
|
|
26
26
|
export function surround<T>(
|
|
27
27
|
opener: string | RegExp | Parser<T>, parser: Parser<T>, closer: string | RegExp | Parser<T>, optional: boolean = false,
|
|
28
28
|
f?: (rss: [T[], T[], T[]], rest: string, context: Ctx) => Result<T>,
|
|
29
|
-
g?: (rss: [T[], T[]], rest: string, context: Ctx) => Result<T>,
|
|
29
|
+
g?: (rss: [T[], T[], string], rest: string, context: Ctx) => Result<T>,
|
|
30
30
|
): Parser<T> {
|
|
31
31
|
switch (typeof opener) {
|
|
32
32
|
case 'string':
|
|
@@ -60,7 +60,7 @@ export function surround<T>(
|
|
|
60
60
|
? f([rl, rm!, rr], rest, context)
|
|
61
61
|
: [push(unshift(rl, rm ?? []), rr), rest]
|
|
62
62
|
: g
|
|
63
|
-
? g([rl, rm
|
|
63
|
+
? g([rl, rm!, mr_], rest, context)
|
|
64
64
|
: undefined;
|
|
65
65
|
};
|
|
66
66
|
}
|
|
@@ -14,7 +14,7 @@ export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
|
|
|
14
14
|
const result = parsers[i](rest, context);
|
|
15
15
|
assert(check(rest, result));
|
|
16
16
|
if (!result) break;
|
|
17
|
-
assert(!context?.delimiters?.match(rest));
|
|
17
|
+
assert(!context?.delimiters?.match(rest, context.precedence));
|
|
18
18
|
nodes = nodes
|
|
19
19
|
? push(nodes, eval(result))
|
|
20
20
|
: eval(result);
|
|
@@ -14,7 +14,7 @@ export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D
|
|
|
14
14
|
const result = parsers[i](rest, context);
|
|
15
15
|
assert(check(rest, result));
|
|
16
16
|
if (!result) return;
|
|
17
|
-
assert(!context?.delimiters?.match(rest));
|
|
17
|
+
assert(!context?.delimiters?.match(rest, context.precedence));
|
|
18
18
|
nodes = nodes
|
|
19
19
|
? push(nodes, eval(result))
|
|
20
20
|
: eval(result);
|
|
@@ -3,6 +3,8 @@ import { Parser, Delimiters, eval, exec, check } from '../parser';
|
|
|
3
3
|
import { memoize, reduce } from 'spica/memoize';
|
|
4
4
|
import { push } from 'spica/array';
|
|
5
5
|
|
|
6
|
+
type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
|
|
7
|
+
|
|
6
8
|
const signature = (pattern: string | RegExp | undefined): string => {
|
|
7
9
|
switch (typeof pattern) {
|
|
8
10
|
case 'undefined':
|
|
@@ -26,31 +28,29 @@ const matcher = memoize(
|
|
|
26
28
|
},
|
|
27
29
|
signature);
|
|
28
30
|
|
|
29
|
-
export function some<P extends Parser<unknown>>(parser: P, until?: string | RegExp | number,
|
|
30
|
-
export function some<T>(parser: Parser<T>, until?: string | RegExp | number,
|
|
31
|
+
export function some<P extends Parser<unknown>>(parser: P, until?: string | RegExp | number, deeps?: readonly DelimiterOption[], limit?: number): P;
|
|
32
|
+
export function some<T>(parser: Parser<T>, until?: string | RegExp | number, deeps: readonly DelimiterOption[] = [], limit = -1): Parser<T> {
|
|
33
|
+
if (typeof until === 'number') return some(parser, undefined, deeps, until);
|
|
31
34
|
assert(parser);
|
|
32
|
-
assert(until instanceof RegExp ? !
|
|
33
|
-
assert(deep instanceof RegExp ? !deep.flags.match(/[gmy]/) && deep.source.startsWith('^') : true);
|
|
34
|
-
if (typeof until === 'number') return some(parser, undefined, deep, until);
|
|
35
|
+
assert([until].concat(deeps.map(o => o[0])).every(d => d instanceof RegExp ? !d.flags.match(/[gmy]/) && d.source.startsWith('^') : true));
|
|
35
36
|
const match = matcher(until);
|
|
36
|
-
const
|
|
37
|
-
signature: signature(
|
|
38
|
-
matcher: matcher(
|
|
39
|
-
|
|
37
|
+
const delimiters = deeps.map(([delimiter, precedence]) => ({
|
|
38
|
+
signature: signature(delimiter),
|
|
39
|
+
matcher: matcher(delimiter),
|
|
40
|
+
precedence,
|
|
41
|
+
}));
|
|
40
42
|
return (source, context) => {
|
|
41
43
|
if (source === '') return;
|
|
42
44
|
let rest = source;
|
|
43
45
|
let nodes: T[] | undefined;
|
|
44
|
-
if (
|
|
45
|
-
// bracket > link > media | bracket
|
|
46
|
-
// bracket > index > bracket
|
|
46
|
+
if (delimiters.length > 0) {
|
|
47
47
|
context.delimiters ??= new Delimiters();
|
|
48
|
-
context.delimiters.push(
|
|
48
|
+
context.delimiters.push(...delimiters);
|
|
49
49
|
}
|
|
50
50
|
while (true) {
|
|
51
51
|
if (rest === '') break;
|
|
52
52
|
if (match(rest)) break;
|
|
53
|
-
if (context.delimiters?.match(rest)) break;
|
|
53
|
+
if (context.delimiters?.match(rest, context.precedence)) break;
|
|
54
54
|
const result = parser(rest, context);
|
|
55
55
|
assert.doesNotThrow(() => limit < 0 && check(rest, result));
|
|
56
56
|
if (!result) break;
|
|
@@ -60,8 +60,8 @@ export function some<T>(parser: Parser<T>, until?: string | RegExp | number, dee
|
|
|
60
60
|
rest = exec(result);
|
|
61
61
|
if (limit >= 0 && source.length - rest.length > limit) break;
|
|
62
62
|
}
|
|
63
|
-
if (
|
|
64
|
-
context.delimiters
|
|
63
|
+
if (delimiters.length > 0) {
|
|
64
|
+
context.delimiters!.pop(delimiters.length);
|
|
65
65
|
}
|
|
66
66
|
assert(rest.length <= source.length);
|
|
67
67
|
return nodes && rest.length < source.length
|
|
@@ -69,25 +69,3 @@ export function some<T>(parser: Parser<T>, until?: string | RegExp | number, dee
|
|
|
69
69
|
: undefined;
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
export function escape<P extends Parser<unknown>>(parser: P, delim: string): P;
|
|
74
|
-
export function escape<T>(parser: Parser<T>, delim: string): Parser<T> {
|
|
75
|
-
assert(parser);
|
|
76
|
-
const delimiter = {
|
|
77
|
-
signature: signature(delim),
|
|
78
|
-
matcher: (source: string) => source.slice(0, delim.length) !== delim && undefined,
|
|
79
|
-
escape: true,
|
|
80
|
-
} as const;
|
|
81
|
-
return (source, context) => {
|
|
82
|
-
if (source === '') return;
|
|
83
|
-
if (context) {
|
|
84
|
-
context.delimiters ??= new Delimiters();
|
|
85
|
-
context.delimiters.push(delimiter);
|
|
86
|
-
}
|
|
87
|
-
const result = parser(source, context);
|
|
88
|
-
if (context.delimiters) {
|
|
89
|
-
context.delimiters.pop();
|
|
90
|
-
}
|
|
91
|
-
return result;
|
|
92
|
-
};
|
|
93
|
-
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { undefined } from 'spica/global';
|
|
2
|
-
|
|
3
1
|
export type Parser<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
4
2
|
= (source: string, context: C) => Result<T, C, D>;
|
|
5
3
|
export type Result<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
@@ -11,6 +9,7 @@ export interface Ctx {
|
|
|
11
9
|
budget: number;
|
|
12
10
|
recursion: number;
|
|
13
11
|
};
|
|
12
|
+
precedence?: number;
|
|
14
13
|
delimiters?: Delimiters;
|
|
15
14
|
}
|
|
16
15
|
export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
|
|
@@ -22,28 +21,44 @@ type ExtractSubTree<D extends Parser<unknown>[]> = ExtractSubParser<D> extends i
|
|
|
22
21
|
type ExtractSubParser<D extends Parser<unknown>[]> = D extends (infer P)[] ? P extends Parser<unknown> ? P : never : never;
|
|
23
22
|
|
|
24
23
|
export class Delimiters {
|
|
25
|
-
private readonly matchers: (
|
|
26
|
-
private readonly
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
private readonly matchers: [number, string, number, (source: string) => boolean | undefined][] = [];
|
|
25
|
+
private readonly registry: Record<string, boolean> = {};
|
|
26
|
+
private length = 0;
|
|
27
|
+
public push(
|
|
28
|
+
...delimiters: readonly {
|
|
29
|
+
readonly signature: string;
|
|
30
|
+
readonly matcher: (source: string) => boolean | undefined;
|
|
31
|
+
readonly precedence?: number;
|
|
32
|
+
}[]
|
|
33
|
+
): void {
|
|
34
|
+
for (let i = 0; i < delimiters.length; ++i) {
|
|
35
|
+
const delimiter = delimiters[i];
|
|
36
|
+
assert(this.length >= this.matchers.length);
|
|
37
|
+
const { signature, matcher, precedence = 1 } = delimiter;
|
|
38
|
+
if (!this.registry[signature]) {
|
|
39
|
+
this.matchers.unshift([this.length, signature, precedence, matcher]);
|
|
40
|
+
this.registry[signature] = true;
|
|
41
|
+
}
|
|
42
|
+
++this.length;
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
|
-
public pop(): void {
|
|
38
|
-
assert(
|
|
39
|
-
|
|
45
|
+
public pop(count = 1): void {
|
|
46
|
+
assert(count > 0);
|
|
47
|
+
for (let i = 0; i < count; ++i) {
|
|
48
|
+
assert(this.matchers.length > 0);
|
|
49
|
+
assert(this.length >= this.matchers.length);
|
|
50
|
+
if (--this.length === this.matchers[0][0]) {
|
|
51
|
+
this.registry[this.matchers.shift()![1]] = false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
40
54
|
}
|
|
41
|
-
public match(source: string): boolean {
|
|
55
|
+
public match(source: string, precedence = 1): boolean {
|
|
42
56
|
const { matchers } = this;
|
|
43
57
|
for (let i = 0; i < matchers.length; ++i) {
|
|
44
|
-
switch (matchers[i](source)) {
|
|
58
|
+
switch (matchers[i][3](source)) {
|
|
45
59
|
case true:
|
|
46
|
-
return true;
|
|
60
|
+
if (precedence < matchers[i][2]) return true;
|
|
61
|
+
continue;
|
|
47
62
|
case false:
|
|
48
63
|
return false;
|
|
49
64
|
}
|
|
@@ -73,6 +88,7 @@ export function exec(result: Result<unknown>, default_?: string): string | undef
|
|
|
73
88
|
|
|
74
89
|
export function check(source: string, result: Result<unknown>, mustConsume = true): true {
|
|
75
90
|
assert.doesNotThrow(() => {
|
|
91
|
+
if (source.length > 1000) return;
|
|
76
92
|
if (source.slice(+mustConsume).slice(-exec(result, '').length || source.length) !== exec(result, '')) throw new Error();
|
|
77
93
|
});
|
|
78
94
|
return true;
|
package/src/debug.test.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { querySelector, querySelectorAll } from 'typed-dom/query';
|
|
|
4
4
|
|
|
5
5
|
export function inspect(result: Result<HTMLElement | string>, until: number | string = Infinity): Result<string> {
|
|
6
6
|
return result && [
|
|
7
|
-
eval(result).map(node => {
|
|
8
|
-
assert(node);
|
|
7
|
+
eval(result).map((node, i, nodes) => {
|
|
8
|
+
assert(node || node === '' && '([{'.includes(nodes[i + 1][0]));
|
|
9
9
|
if (typeof node === 'string') return node;
|
|
10
10
|
node = node.cloneNode(true);
|
|
11
11
|
assert(!querySelector(node, '.invalid[data-invalid-message$="."]'));
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { undefined, location } from 'spica/global';
|
|
2
|
-
import { ObjectAssign, ObjectCreate } from 'spica/alias';
|
|
3
2
|
import { ParserSettings, Progress } from '../../..';
|
|
4
3
|
import { MarkdownParser } from '../../../markdown';
|
|
5
4
|
import { eval } from '../../combinator/data/parser';
|
|
@@ -21,11 +20,10 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
21
20
|
nearest: (position: number) => HTMLElement | undefined;
|
|
22
21
|
index: (block: HTMLElement) => number;
|
|
23
22
|
} {
|
|
24
|
-
|
|
23
|
+
let context: MarkdownParser.Context = {
|
|
24
|
+
...settings,
|
|
25
25
|
host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
26
|
-
|
|
27
|
-
chunk: undefined,
|
|
28
|
-
});
|
|
26
|
+
};
|
|
29
27
|
if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
|
|
30
28
|
assert(!settings.id);
|
|
31
29
|
type Block = readonly [segment: string, blocks: readonly HTMLElement[], url: string];
|
|
@@ -41,14 +39,14 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
41
39
|
};
|
|
42
40
|
|
|
43
41
|
function* parse(source: string): Generator<Progress, undefined, undefined> {
|
|
44
|
-
if (settings.chunk && revision) throw new Error('Chunks cannot be updated
|
|
42
|
+
if (settings.chunk && revision) throw new Error('Chunks cannot be updated');
|
|
45
43
|
const url = headers(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
|
|
46
44
|
source = normalize(validate(source, MAX_INPUT_SIZE) ? source : source.slice(0, MAX_INPUT_SIZE + 1));
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
// Change the object identity.
|
|
46
|
+
context = {
|
|
47
|
+
...context,
|
|
48
|
+
url: url ? new ReadonlyURL(url as ':') : undefined,
|
|
49
|
+
};
|
|
52
50
|
const rev = revision = Symbol();
|
|
53
51
|
const sourceSegments: string[] = [];
|
|
54
52
|
for (const seg of segment(source)) {
|
|
@@ -223,22 +223,64 @@ describe('Unit: parser/api/parse', () => {
|
|
|
223
223
|
['<p>a<span class="linebreak"> </span>b</p>']);
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
-
it('
|
|
226
|
+
it('backtrack', () => {
|
|
227
227
|
assert.deepStrictEqual(
|
|
228
|
-
[...parse('"[% '.repeat(100)).children].map(el => el.outerHTML),
|
|
229
|
-
[
|
|
228
|
+
[...parse('"[% '.repeat(100) + '\n\na').children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
229
|
+
[
|
|
230
|
+
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
231
|
+
`<pre class="error" translate="no">${'"[% '.repeat(100)}\n</pre>`,
|
|
232
|
+
'<p>a</p>',
|
|
233
|
+
]);
|
|
230
234
|
});
|
|
231
235
|
|
|
232
236
|
it('recursion', () => {
|
|
233
237
|
assert.deepStrictEqual(
|
|
234
|
-
[...parse('('.repeat(
|
|
235
|
-
[`<p>${'('.repeat(
|
|
238
|
+
[...parse('('.repeat(20)).children].map(el => el.outerHTML),
|
|
239
|
+
[`<p>${'('.repeat(20)}</p>`]);
|
|
236
240
|
assert.deepStrictEqual(
|
|
237
|
-
[...parse('('.repeat(
|
|
241
|
+
[...parse('('.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
238
242
|
[
|
|
239
|
-
'<h1 id="error:rnd" class="error">Error: Too much recursion
|
|
240
|
-
`<pre class="error" translate="no">${'('.repeat(
|
|
241
|
-
|
|
243
|
+
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
244
|
+
`<pre class="error" translate="no">${'('.repeat(21)}</pre>`,
|
|
245
|
+
]);
|
|
246
|
+
assert.deepStrictEqual(
|
|
247
|
+
[...parse('['.repeat(20)).children].map(el => el.outerHTML),
|
|
248
|
+
[`<p>${'['.repeat(20)}</p>`]);
|
|
249
|
+
assert.deepStrictEqual(
|
|
250
|
+
[...parse('['.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
251
|
+
[
|
|
252
|
+
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
253
|
+
`<pre class="error" translate="no">${'['.repeat(21)}</pre>`,
|
|
254
|
+
]);
|
|
255
|
+
assert.deepStrictEqual(
|
|
256
|
+
[...parse('{'.repeat(20)).children].map(el => el.outerHTML),
|
|
257
|
+
[`<p>${'{'.repeat(20)}</p>`]);
|
|
258
|
+
assert.deepStrictEqual(
|
|
259
|
+
[...parse('{'.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
260
|
+
[
|
|
261
|
+
'<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
|
|
262
|
+
`<pre class="error" translate="no">${'{'.repeat(21)}</pre>`,
|
|
263
|
+
]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!navigator.userAgent.includes('Chrome')) return;
|
|
267
|
+
|
|
268
|
+
it('creation', function () {
|
|
269
|
+
this.timeout(5000);
|
|
270
|
+
// 実測500ms程度
|
|
271
|
+
assert.deepStrictEqual(
|
|
272
|
+
[...parse('.'.repeat(50000)).children].map(el => el.outerHTML),
|
|
273
|
+
[`<p>${'.'.repeat(50000)}</p>`]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('creation error', function () {
|
|
277
|
+
this.timeout(5000);
|
|
278
|
+
// 実測500ms程度
|
|
279
|
+
assert.deepStrictEqual(
|
|
280
|
+
[...parse('.'.repeat(50001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
281
|
+
[
|
|
282
|
+
'<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
|
|
283
|
+
`<pre class="error" translate="no">${'.'.repeat(1000).slice(0, 997)}...</pre>`,
|
|
242
284
|
]);
|
|
243
285
|
});
|
|
244
286
|
|
package/src/parser/block.ts
CHANGED
|
@@ -36,7 +36,7 @@ export import ReplyParser = BlockParser.ReplyParser;
|
|
|
36
36
|
export import ParagraphParser = BlockParser.ParagraphParser;
|
|
37
37
|
|
|
38
38
|
export const block: BlockParser = creator(error(
|
|
39
|
-
reset({ resources: { budget:
|
|
39
|
+
reset({ resources: { budget: 50 * 1000, recursion: 20 + 1 } },
|
|
40
40
|
union([
|
|
41
41
|
emptyline,
|
|
42
42
|
horizontalrule,
|