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