securemark 0.256.0 → 0.257.2
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/README.md +21 -8
- package/dist/index.js +237 -197
- package/markdown.d.ts +23 -17
- package/package.json +1 -1
- package/src/combinator/control/manipulation/context.ts +13 -2
- package/src/combinator/control/manipulation/resource.ts +36 -2
- 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 +48 -11
- 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 +13 -7
- package/src/parser/inline/bracket.ts +10 -10
- package/src/parser/inline/comment.test.ts +5 -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 +1 -0
- 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 -17
- package/src/parser/inline/mark.ts +3 -3
- package/src/parser/inline/math.test.ts +21 -14
- package/src/parser/inline/math.ts +7 -19
- package/src/parser/inline/media.test.ts +0 -2
- package/src/parser/inline/media.ts +10 -10
- package/src/parser/inline/reference.test.ts +6 -5
- 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 -8
- package/src/parser/inline.ts +1 -0
- package/src/parser/util.ts +35 -19
package/markdown.d.ts
CHANGED
|
@@ -671,7 +671,7 @@ export namespace MarkdownParser {
|
|
|
671
671
|
export interface AnnotationParser extends
|
|
672
672
|
// ((abc))
|
|
673
673
|
Inline<'annotation'>,
|
|
674
|
-
Parser<HTMLElement, Context, [
|
|
674
|
+
Parser<HTMLElement | string, Context, [
|
|
675
675
|
InlineParser,
|
|
676
676
|
]> {
|
|
677
677
|
}
|
|
@@ -680,7 +680,7 @@ export namespace MarkdownParser {
|
|
|
680
680
|
// [[^abbr]]
|
|
681
681
|
// [[^abbr| abc]]
|
|
682
682
|
Inline<'reference'>,
|
|
683
|
-
Parser<HTMLElement, Context, [
|
|
683
|
+
Parser<HTMLElement | string, Context, [
|
|
684
684
|
ReferenceParser.AbbrParser,
|
|
685
685
|
InlineParser,
|
|
686
686
|
InlineParser,
|
|
@@ -740,7 +740,6 @@ export namespace MarkdownParser {
|
|
|
740
740
|
MathParser.BracketParser,
|
|
741
741
|
Parser<string, Context, [
|
|
742
742
|
MathParser.BracketParser,
|
|
743
|
-
MathParser.QuoteParser,
|
|
744
743
|
SourceParser.StrParser,
|
|
745
744
|
]>,
|
|
746
745
|
]> {
|
|
@@ -753,14 +752,6 @@ export namespace MarkdownParser {
|
|
|
753
752
|
SourceParser.EscapableSourceParser,
|
|
754
753
|
]> {
|
|
755
754
|
}
|
|
756
|
-
export interface QuoteParser extends
|
|
757
|
-
Inline<'math/quote'>,
|
|
758
|
-
Parser<HTMLElement, Context, [
|
|
759
|
-
QuoteParser,
|
|
760
|
-
BracketParser,
|
|
761
|
-
SourceParser.StrParser,
|
|
762
|
-
]> {
|
|
763
|
-
}
|
|
764
755
|
}
|
|
765
756
|
export interface ExtensionParser extends
|
|
766
757
|
// [#abc]
|
|
@@ -862,11 +853,20 @@ export namespace MarkdownParser {
|
|
|
862
853
|
// { uri }
|
|
863
854
|
// [abc]{uri nofollow}
|
|
864
855
|
Inline<'link'>,
|
|
865
|
-
Parser<
|
|
856
|
+
Parser<HTMLElement | string, Context, [
|
|
866
857
|
LinkParser.ContentParser,
|
|
867
858
|
LinkParser.ParameterParser,
|
|
868
859
|
]> {
|
|
869
860
|
}
|
|
861
|
+
export interface TextLinkParser extends
|
|
862
|
+
// { uri }
|
|
863
|
+
// [abc]{uri nofollow}
|
|
864
|
+
Inline<'textlink'>,
|
|
865
|
+
Parser<HTMLAnchorElement, Context, [
|
|
866
|
+
LinkParser.TextParser,
|
|
867
|
+
LinkParser.ParameterParser,
|
|
868
|
+
]> {
|
|
869
|
+
}
|
|
870
870
|
export namespace LinkParser {
|
|
871
871
|
export interface ContentParser extends
|
|
872
872
|
Inline<'link/content'>,
|
|
@@ -876,6 +876,12 @@ export namespace MarkdownParser {
|
|
|
876
876
|
InlineParser,
|
|
877
877
|
]> {
|
|
878
878
|
}
|
|
879
|
+
export interface TextParser extends
|
|
880
|
+
Inline<'link/text'>,
|
|
881
|
+
Parser<string[], Context, [
|
|
882
|
+
SourceParser.UnescapableSourceParser,
|
|
883
|
+
]> {
|
|
884
|
+
}
|
|
879
885
|
export interface ParameterParser extends
|
|
880
886
|
Inline<'link/parameter'>,
|
|
881
887
|
Parser<string[], Context, [
|
|
@@ -1100,7 +1106,7 @@ export namespace MarkdownParser {
|
|
|
1100
1106
|
// https://host
|
|
1101
1107
|
Inline<'url'>,
|
|
1102
1108
|
Parser<HTMLAnchorElement, Context, [
|
|
1103
|
-
|
|
1109
|
+
TextLinkParser,
|
|
1104
1110
|
]> {
|
|
1105
1111
|
}
|
|
1106
1112
|
export namespace UrlParser {
|
|
@@ -1142,28 +1148,28 @@ export namespace MarkdownParser {
|
|
|
1142
1148
|
// @user
|
|
1143
1149
|
Inline<'account'>,
|
|
1144
1150
|
Parser<HTMLAnchorElement, Context, [
|
|
1145
|
-
|
|
1151
|
+
TextLinkParser,
|
|
1146
1152
|
]> {
|
|
1147
1153
|
}
|
|
1148
1154
|
export interface HashtagParser extends
|
|
1149
1155
|
// #tag
|
|
1150
1156
|
Inline<'hashtag'>,
|
|
1151
1157
|
Parser<HTMLAnchorElement, Context, [
|
|
1152
|
-
|
|
1158
|
+
TextLinkParser,
|
|
1153
1159
|
]> {
|
|
1154
1160
|
}
|
|
1155
1161
|
export interface HashnumParser extends
|
|
1156
1162
|
// #1
|
|
1157
1163
|
Inline<'hashnum'>,
|
|
1158
1164
|
Parser<HTMLAnchorElement, Context, [
|
|
1159
|
-
|
|
1165
|
+
TextLinkParser,
|
|
1160
1166
|
]> {
|
|
1161
1167
|
}
|
|
1162
1168
|
export interface AnchorParser extends
|
|
1163
1169
|
// >>1
|
|
1164
1170
|
Inline<'anchor'>,
|
|
1165
1171
|
Parser<HTMLAnchorElement, Context, [
|
|
1166
|
-
|
|
1172
|
+
TextLinkParser,
|
|
1167
1173
|
]> {
|
|
1168
1174
|
}
|
|
1169
1175
|
}
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
|
@@ -7,8 +7,8 @@ export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>
|
|
|
7
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,27 +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
|
+
]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('recursion', () => {
|
|
237
|
+
assert.deepStrictEqual(
|
|
238
|
+
[...parse('('.repeat(20)).children].map(el => el.outerHTML),
|
|
239
|
+
[`<p>${'('.repeat(20)}</p>`]);
|
|
240
|
+
assert.deepStrictEqual(
|
|
241
|
+
[...parse('('.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
242
|
+
[
|
|
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
|
+
]);
|
|
230
264
|
});
|
|
231
265
|
|
|
232
266
|
if (!navigator.userAgent.includes('Chrome')) return;
|
|
233
267
|
|
|
234
|
-
it('
|
|
268
|
+
it('creation', function () {
|
|
269
|
+
this.timeout(5000);
|
|
270
|
+
// 実測500ms程度
|
|
235
271
|
assert.deepStrictEqual(
|
|
236
|
-
[...parse('
|
|
237
|
-
[`<p>${'
|
|
272
|
+
[...parse('.'.repeat(50000)).children].map(el => el.outerHTML),
|
|
273
|
+
[`<p>${'.'.repeat(50000)}</p>`]);
|
|
238
274
|
});
|
|
239
275
|
|
|
240
|
-
it('
|
|
276
|
+
it('creation error', function () {
|
|
277
|
+
this.timeout(5000);
|
|
278
|
+
// 実測500ms程度
|
|
241
279
|
assert.deepStrictEqual(
|
|
242
|
-
[...parse('
|
|
280
|
+
[...parse('.'.repeat(50001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
243
281
|
[
|
|
244
|
-
'<h1 id="error:rnd" class="error">Error: Too
|
|
245
|
-
`<pre class="error" translate="no">${'
|
|
246
|
-
'<p>a</p>',
|
|
282
|
+
'<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
|
|
283
|
+
`<pre class="error" translate="no">${'.'.repeat(1000).slice(0, 997)}...</pre>`,
|
|
247
284
|
]);
|
|
248
285
|
});
|
|
249
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: 50 * 1000, recursion:
|
|
39
|
+
reset({ resources: { budget: 50 * 1000, recursion: 20 + 1 } },
|
|
40
40
|
union([
|
|
41
41
|
emptyline,
|
|
42
42
|
horizontalrule,
|
|
@@ -14,14 +14,15 @@ describe('Unit: parser/inline/annotation', () => {
|
|
|
14
14
|
assert.deepStrictEqual(inspect(parser('(())')), undefined);
|
|
15
15
|
assert.deepStrictEqual(inspect(parser('(()))')), undefined);
|
|
16
16
|
assert.deepStrictEqual(inspect(parser('(( ))')), undefined);
|
|
17
|
+
assert.deepStrictEqual(inspect(parser('(( (a')), [['', '(('], ' (a']);
|
|
17
18
|
assert.deepStrictEqual(inspect(parser('((\n))')), undefined);
|
|
18
19
|
assert.deepStrictEqual(inspect(parser('((\na))')), undefined);
|
|
19
20
|
assert.deepStrictEqual(inspect(parser('((\\\na))')), undefined);
|
|
20
|
-
assert.deepStrictEqual(inspect(parser('((a\n))')),
|
|
21
|
-
assert.deepStrictEqual(inspect(parser('((a\\\n))')),
|
|
22
|
-
assert.deepStrictEqual(inspect(parser('((a\nb))')),
|
|
23
|
-
assert.deepStrictEqual(inspect(parser('((a\\\nb))')),
|
|
24
|
-
assert.deepStrictEqual(inspect(parser('((*a\nb*))')),
|
|
21
|
+
assert.deepStrictEqual(inspect(parser('((a\n))')), [['', '(('], 'a\n))']);
|
|
22
|
+
assert.deepStrictEqual(inspect(parser('((a\\\n))')), [['', '(('], 'a\\\n))']);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser('((a\nb))')), [['', '(('], 'a\nb))']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser('((a\\\nb))')), [['', '(('], 'a\\\nb))']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser('((*a\nb*))')), [['', '(('], '*a\nb*))']);
|
|
25
26
|
assert.deepStrictEqual(inspect(parser('((\\))')), undefined);
|
|
26
27
|
assert.deepStrictEqual(inspect(parser('((a)b))')), undefined);
|
|
27
28
|
assert.deepStrictEqual(inspect(parser('(((a))')), undefined);
|
|
@@ -42,6 +43,7 @@ describe('Unit: parser/inline/annotation', () => {
|
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
it('nest', () => {
|
|
46
|
+
assert.deepStrictEqual(inspect(parser('((<bdi>))')), [['<sup class="annotation"><span><span class="invalid"><bdi></span></span></sup>'], '']);
|
|
45
47
|
assert.deepStrictEqual(inspect(parser('((`a`))')), [['<sup class="annotation"><span><code data-src="`a`">a</code></span></sup>'], '']);
|
|
46
48
|
assert.deepStrictEqual(inspect(parser('((@a))')), [['<sup class="annotation"><span><a href="/@a" class="account">@a</a></span></sup>'], '']);
|
|
47
49
|
assert.deepStrictEqual(inspect(parser('((http://host))')), [['<sup class="annotation"><span><a href="http://host" target="_blank">http://host</a></span></sup>'], '']);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { undefined } from 'spica/global';
|
|
2
2
|
import { AnnotationParser } from '../inline';
|
|
3
|
-
import { union, some, validate, guard, context, creator, surround, lazy
|
|
3
|
+
import { union, some, validate, guard, context, precedence, creator, recursion, surround, lazy } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
|
-
import {
|
|
5
|
+
import { optimize } from './link';
|
|
6
|
+
import { startLoose, trimNode } from '../util';
|
|
6
7
|
import { html, defrag } from 'typed-dom/dom';
|
|
7
8
|
|
|
8
|
-
export const annotation: AnnotationParser = lazy(() => creator(validate('((',
|
|
9
|
+
export const annotation: AnnotationParser = lazy(() => creator(recursion(precedence(6, validate('((', surround(
|
|
9
10
|
'((',
|
|
10
11
|
guard(context => context.syntax?.inline?.annotation ?? true,
|
|
12
|
+
startLoose(
|
|
11
13
|
context({ syntax: { inline: {
|
|
12
14
|
annotation: false,
|
|
13
15
|
// Redundant
|
|
@@ -19,6 +21,8 @@ export const annotation: AnnotationParser = lazy(() => creator(validate('((', fm
|
|
|
19
21
|
//link: true,
|
|
20
22
|
//autolink: true,
|
|
21
23
|
}}, delimiters: undefined },
|
|
22
|
-
|
|
23
|
-
'))'
|
|
24
|
-
|
|
24
|
+
some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')),
|
|
25
|
+
'))',
|
|
26
|
+
false,
|
|
27
|
+
([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest],
|
|
28
|
+
([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest)))))));
|