securemark 0.289.4 → 0.289.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/design.md +3 -3
- package/dist/index.js +104 -82
- package/package.json +1 -1
- package/src/combinator/control/constraint/contract.ts +3 -6
- package/src/combinator/control/manipulation/match.ts +1 -1
- package/src/combinator/control/manipulation/surround.ts +69 -39
- package/src/combinator/control/monad/bind.ts +1 -0
- package/src/combinator/data/parser.ts +0 -1
- package/src/parser/api/parse.test.ts +7 -6
- package/src/parser/context.ts +5 -11
- package/src/parser/inline/annotation.ts +5 -5
- package/src/parser/inline/autolink/email.ts +11 -5
- package/src/parser/inline/autolink/hashtag.ts +4 -2
- package/src/parser/inline/autolink/url.ts +12 -8
- package/src/parser/inline/code.ts +13 -6
- package/src/parser/inline/extension/index.ts +5 -5
- package/src/parser/inline/extension/label.ts +1 -1
- package/src/parser/inline/html.ts +10 -4
- package/src/parser/inline/link.ts +24 -9
- package/src/parser/inline/media.ts +12 -7
- package/src/parser/inline/reference.ts +15 -11
- package/src/parser/inline/ruby.ts +17 -14
- package/src/parser/inline/template.ts +10 -7
- package/src/parser/source/escapable.ts +0 -1
- package/src/parser/source/unescapable.ts +0 -1
|
@@ -7,7 +7,6 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
7
7
|
f?: (rss: [S[], SubNode<P>[], S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
8
8
|
g?: (rss: [S[], SubNode<P>[], string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
9
9
|
backtracks?: readonly number[],
|
|
10
|
-
backtrackstate?: number,
|
|
11
10
|
): P;
|
|
12
11
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
13
12
|
opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>,
|
|
@@ -15,7 +14,6 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
15
14
|
f?: (rss: [S[], SubNode<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
16
15
|
g?: (rss: [S[], SubNode<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
17
16
|
backtracks?: readonly number[],
|
|
18
|
-
backtrackstate?: number,
|
|
19
17
|
): P;
|
|
20
18
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
21
19
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
@@ -23,7 +21,6 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
23
21
|
f?: (rss: [S[], Node<P>[], S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
24
22
|
g?: (rss: [S[], Node<P>[], string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
25
23
|
backtracks?: readonly number[],
|
|
26
|
-
backtrackstate?: number,
|
|
27
24
|
): P;
|
|
28
25
|
export function surround<P extends Parser<unknown>, S = string>(
|
|
29
26
|
opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
@@ -31,7 +28,6 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
31
28
|
f?: (rss: [S[], Node<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
32
29
|
g?: (rss: [S[], Node<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
33
30
|
backtracks?: readonly number[],
|
|
34
|
-
backtrackstate?: number,
|
|
35
31
|
): P;
|
|
36
32
|
export function surround<N>(
|
|
37
33
|
opener: string | RegExp | Parser<N>, parser: Parser<N>, closer: string | RegExp | Parser<N>,
|
|
@@ -39,7 +35,6 @@ export function surround<N>(
|
|
|
39
35
|
f?: (rss: [N[], N[], N[]], rest: string, context: Ctx) => Result<N>,
|
|
40
36
|
g?: (rss: [N[], N[], string], rest: string, context: Ctx) => Result<N>,
|
|
41
37
|
backtracks: readonly number[] = [],
|
|
42
|
-
backtrackstate: number = 0,
|
|
43
38
|
): Parser<N> {
|
|
44
39
|
switch (typeof opener) {
|
|
45
40
|
case 'string':
|
|
@@ -51,7 +46,6 @@ export function surround<N>(
|
|
|
51
46
|
case 'object':
|
|
52
47
|
closer = match(closer);
|
|
53
48
|
}
|
|
54
|
-
const statesize = 2;
|
|
55
49
|
return ({ source, context }) => {
|
|
56
50
|
const sme_ = source;
|
|
57
51
|
if (sme_ === '') return;
|
|
@@ -62,41 +56,18 @@ export function surround<N>(
|
|
|
62
56
|
if (resultS === undefined) return void revert(context, linebreak);
|
|
63
57
|
const nodesS = eval(resultS);
|
|
64
58
|
const me_ = exec(resultS);
|
|
65
|
-
|
|
66
|
-
if (backtrack & 1) {
|
|
67
|
-
const { backtracks = {}, backtrack: state = 0, offset = 0 } = context;
|
|
68
|
-
for (let i = 0; i < source.length - me_.length; ++i) {
|
|
69
|
-
if (source[i] !== source[0]) break;
|
|
70
|
-
const pos = source.length - i + offset - 1;
|
|
71
|
-
assert(pos >= 0);
|
|
72
|
-
if (!(pos in backtracks)) continue;
|
|
73
|
-
const shift = backtrack >>> statesize & state >>> statesize ? state & (1 << statesize) - 1 : 0;
|
|
74
|
-
if (backtracks[pos] & 1 << size(backtrack >>> statesize) + shift) return void revert(context, linebreak);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const { backtrack = 0 } = context;
|
|
79
|
-
context.backtrack = backtrack | backtrackstate;
|
|
59
|
+
if (getBacktrack(context, backtracks, sme_, sme_.length - me_.length)) return void revert(context, linebreak);
|
|
80
60
|
const resultM = me_ !== '' ? parser({ source: me_, context }) : undefined;
|
|
81
61
|
assert(check(me_, resultM));
|
|
82
|
-
context.backtrack = backtrack;
|
|
83
62
|
const nodesM = eval(resultM);
|
|
84
63
|
const e_ = exec(resultM, me_);
|
|
85
|
-
|
|
86
|
-
const resultE = closer({ source: e_, context });
|
|
64
|
+
const resultE = nodesM || optional ? closer({ source: e_, context }) : undefined;
|
|
87
65
|
assert(check(e_, resultE, false));
|
|
88
66
|
const nodesE = eval(resultE);
|
|
89
67
|
const rest = exec(resultE, e_);
|
|
68
|
+
nodesE || setBacktrack(context, backtracks, sme_.length);
|
|
69
|
+
if (!nodesM && !optional) return void revert(context, linebreak);
|
|
90
70
|
if (rest.length === sme_.length) return void revert(context, linebreak);
|
|
91
|
-
for (const backtrack of backtracks) {
|
|
92
|
-
if (backtrack & 2 && nodesE === undefined) {
|
|
93
|
-
const { backtracks = {}, backtrack: state = 0, offset = 0 } = context;
|
|
94
|
-
const pos = source.length + offset - 1;
|
|
95
|
-
assert(pos >= 0);
|
|
96
|
-
const shift = backtrack >>> statesize & state >>> statesize ? state & (1 << statesize) - 1 : 0;
|
|
97
|
-
backtracks[pos] |= 1 << size(backtrack >>> statesize) + shift;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
71
|
context.recent = [
|
|
101
72
|
sme_.slice(0, sme_.length - me_.length),
|
|
102
73
|
me_.slice(0, me_.length - e_.length),
|
|
@@ -118,13 +89,72 @@ export function surround<N>(
|
|
|
118
89
|
return result;
|
|
119
90
|
};
|
|
120
91
|
}
|
|
121
|
-
export function open<P extends Parser<unknown>>(
|
|
122
|
-
|
|
123
|
-
|
|
92
|
+
export function open<P extends Parser<unknown>>(
|
|
93
|
+
opener: string | RegExp | Parser<Node<P>, Context<P>>,
|
|
94
|
+
parser: P,
|
|
95
|
+
optional?: boolean,
|
|
96
|
+
backtracks?: readonly number[],
|
|
97
|
+
): P;
|
|
98
|
+
export function open<N>(
|
|
99
|
+
opener: string | RegExp | Parser<N>,
|
|
100
|
+
parser: Parser<N>,
|
|
101
|
+
optional?: boolean,
|
|
102
|
+
backtracks?: readonly number[],
|
|
103
|
+
): Parser<N> {
|
|
104
|
+
return surround(opener, parser, '', optional, undefined, undefined, backtracks);
|
|
105
|
+
}
|
|
106
|
+
export function close<P extends Parser<unknown>>(
|
|
107
|
+
parser: P,
|
|
108
|
+
closer: string | RegExp | Parser<Node<P>, Context<P>>,
|
|
109
|
+
optional?: boolean,
|
|
110
|
+
backtracks?: readonly number[],
|
|
111
|
+
): P;
|
|
112
|
+
export function close<N>(
|
|
113
|
+
parser: Parser<N>,
|
|
114
|
+
closer: string | RegExp | Parser<N>,
|
|
115
|
+
optional?: boolean,
|
|
116
|
+
backtracks?: readonly number[],
|
|
117
|
+
): Parser<N> {
|
|
118
|
+
return surround('', parser, closer, optional, undefined, undefined, backtracks);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const statesize = 2;
|
|
122
|
+
export function getBacktrack(
|
|
123
|
+
context: Ctx,
|
|
124
|
+
backtracks: readonly number[],
|
|
125
|
+
source: string,
|
|
126
|
+
length: number,
|
|
127
|
+
): boolean {
|
|
128
|
+
for (const backtrack of backtracks) {
|
|
129
|
+
if (backtrack & 1) {
|
|
130
|
+
const { backtracks = {}, offset = 0 } = context;
|
|
131
|
+
for (let i = 0; i < length; ++i) {
|
|
132
|
+
if (source[i] !== source[0]) break;
|
|
133
|
+
const pos = source.length - i + offset - 1;
|
|
134
|
+
assert(pos >= 0);
|
|
135
|
+
if (!(pos in backtracks)) continue;
|
|
136
|
+
if (backtracks[pos] & 1 << size(backtrack >>> statesize)) return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
124
141
|
}
|
|
125
|
-
export function
|
|
126
|
-
|
|
127
|
-
|
|
142
|
+
export function setBacktrack(
|
|
143
|
+
context: Ctx,
|
|
144
|
+
backtracks: readonly number[],
|
|
145
|
+
position: number,
|
|
146
|
+
length: number = 1,
|
|
147
|
+
): void {
|
|
148
|
+
for (const backtrack of backtracks) {
|
|
149
|
+
if (backtrack & 2) {
|
|
150
|
+
const { backtracks = {}, offset = 0 } = context;
|
|
151
|
+
for (let i = 0; i < length; ++i) {
|
|
152
|
+
const pos = position - i + offset - 1;
|
|
153
|
+
assert(pos >= 0);
|
|
154
|
+
backtracks[pos] |= 1 << size(backtrack >>> statesize);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
128
158
|
}
|
|
129
159
|
|
|
130
160
|
function match(pattern: string | RegExp): (input: Input) => [never[], string] | undefined {
|
|
@@ -12,6 +12,7 @@ export function bind<N, U>(parser: Parser<N>, f: (nodes: N[], rest: string, cont
|
|
|
12
12
|
const res1 = parser(input);
|
|
13
13
|
assert(check(source, res1));
|
|
14
14
|
if (res1 === undefined) return;
|
|
15
|
+
context.recent = [source.slice(0, source.length - exec(res1).length)];
|
|
15
16
|
const res2 = f(eval(res1), exec(res1), context);
|
|
16
17
|
assert(check(source, res2));
|
|
17
18
|
assert(check(exec(res1), res2, false));
|
|
@@ -350,20 +350,21 @@ describe('Unit: parser/api/parse', () => {
|
|
|
350
350
|
|
|
351
351
|
it('backtrack', function () {
|
|
352
352
|
this.timeout(5000);
|
|
353
|
-
|
|
353
|
+
// 8n = template + link + annotation/reference + link + code + url + ruby + text
|
|
354
|
+
const source = `${'.'.repeat(1 + 0)}{{(([[[\`http://[${'.'.repeat(12493)}`;
|
|
354
355
|
assert.deepStrictEqual(
|
|
355
|
-
[...parse(
|
|
356
|
-
[`<p>${
|
|
356
|
+
[...parse(source).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
357
|
+
[`<p>${source}</p>`]);
|
|
357
358
|
});
|
|
358
359
|
|
|
359
360
|
it('backtrack error', function () {
|
|
360
361
|
this.timeout(5000);
|
|
361
|
-
const
|
|
362
|
+
const source = `${'.'.repeat(1 + 1)}{{(([[[\`http://[${'.'.repeat(12493)}`;
|
|
362
363
|
assert.deepStrictEqual(
|
|
363
|
-
[...parse(
|
|
364
|
+
[...parse(source).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
|
|
364
365
|
[
|
|
365
366
|
'<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
|
|
366
|
-
`<pre class="error" translate="no">${
|
|
367
|
+
`<pre class="error" translate="no">${source.slice(0, 1000 - 3)}...</pre>`,
|
|
367
368
|
]);
|
|
368
369
|
});
|
|
369
370
|
|
package/src/parser/context.ts
CHANGED
|
@@ -28,18 +28,12 @@ export const enum Recursion {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export const enum Backtrack {
|
|
31
|
-
link = 1 <<
|
|
32
|
-
ruby = 1 <<
|
|
33
|
-
|
|
34
|
-
linebracket = 1 << 4,
|
|
31
|
+
link = 1 << 6,
|
|
32
|
+
ruby = 1 << 5,
|
|
33
|
+
doublebracket = 1 << 4,
|
|
35
34
|
bracket = 1 << 3,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
// バックトラックを削減するため括弧派生構文を改行禁止し
|
|
40
|
-
// 括弧派生構文内のバックトラック状態を統一する。
|
|
41
|
-
export const enum BacktrackState {
|
|
42
|
-
nobreak = 1,
|
|
35
|
+
escbracket = 1 << 2,
|
|
36
|
+
autolink = 0 << 2,
|
|
43
37
|
}
|
|
44
38
|
|
|
45
39
|
export const enum Command {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnnotationParser } from '../inline';
|
|
2
|
-
import { State, Backtrack
|
|
2
|
+
import { State, Backtrack } from '../context';
|
|
3
3
|
import { union, some, precedence, state, constraint, surround, lazy } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
5
|
import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
|
|
@@ -8,13 +8,13 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
8
8
|
export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, false, surround(
|
|
9
9
|
'((',
|
|
10
10
|
precedence(1, state(State.annotation | State.media,
|
|
11
|
-
trimBlankStart(some(union([inline]), ')', [['
|
|
11
|
+
trimBlankStart(some(union([inline]), ')', [[')', 1]])))),
|
|
12
12
|
'))',
|
|
13
13
|
false,
|
|
14
|
-
([, ns], rest) =>
|
|
14
|
+
([, ns], rest, context) =>
|
|
15
|
+
context.linebreak === undefined &&
|
|
15
16
|
trimBlankNodeEnd(ns).length > 0
|
|
16
17
|
? [[html('sup', { class: 'annotation' }, [html('span', defrag(ns))])], rest]
|
|
17
18
|
: undefined,
|
|
18
19
|
undefined,
|
|
19
|
-
[3 | Backtrack.
|
|
20
|
-
Backtrack.bracket | BacktrackState.nobreak)));
|
|
20
|
+
[3 | Backtrack.doublebracket, 1 | Backtrack.bracket])));
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { State } from '../../context';
|
|
3
|
-
import { union, state, constraint, verify, rewrite } from '../../../combinator';
|
|
2
|
+
import { State, Backtrack } from '../../context';
|
|
3
|
+
import { union, state, constraint, verify, rewrite, surround } from '../../../combinator';
|
|
4
4
|
import { str } from '../../source';
|
|
5
5
|
import { html } from 'typed-dom/dom';
|
|
6
6
|
|
|
7
7
|
// https://html.spec.whatwg.org/multipage/input.html
|
|
8
8
|
|
|
9
|
-
export const email: AutolinkParser.EmailParser = rewrite(
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export const email: AutolinkParser.EmailParser = rewrite(
|
|
10
|
+
surround(
|
|
11
|
+
str(/^[0-9a-z]/i),
|
|
12
|
+
verify(
|
|
13
|
+
str(/^(?:[_.+-](?=[0-9a-z])|[0-9a-z]){0,255}@[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*(?![0-9a-z])/i),
|
|
14
|
+
([source]) => source.length <= 255 - 1),
|
|
15
|
+
'',
|
|
16
|
+
false, undefined, undefined,
|
|
17
|
+
[3 | Backtrack.autolink]),
|
|
12
18
|
union([
|
|
13
19
|
constraint(State.autolink, false, state(State.autolink,
|
|
14
20
|
({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], ''])),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutolinkParser } from '../../inline';
|
|
2
|
-
import { State } from '../../context';
|
|
2
|
+
import { State, Backtrack } from '../../context';
|
|
3
3
|
import { union, state, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
4
4
|
import { unsafelink } from '../link';
|
|
5
5
|
import { str } from '../../source';
|
|
@@ -16,7 +16,9 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => rewrite(
|
|
|
16
16
|
str(new RegExp([
|
|
17
17
|
/^(?!['_])(?=(?:[0-9]{1,9})?(?:[^\d\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^'\p{C}\p{S}\p{P}\s]|emoji)))/u.source,
|
|
18
18
|
/(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^'\p{C}\p{S}\p{P}\s]|emoji))+/u.source,
|
|
19
|
-
].join('').replace(/emoji/g, emoji), 'u'))
|
|
19
|
+
].join('').replace(/emoji/g, emoji), 'u')),
|
|
20
|
+
false,
|
|
21
|
+
[3 | Backtrack.autolink]),
|
|
20
22
|
union([
|
|
21
23
|
constraint(State.autolink, false, state(State.autolink, fmap(convert(
|
|
22
24
|
source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`,
|
|
@@ -4,15 +4,17 @@ import { union, tails, some, recursion, precedence, state, constraint, validate,
|
|
|
4
4
|
import { unsafelink } from '../link';
|
|
5
5
|
import { linebreak, unescsource, str } from '../../source';
|
|
6
6
|
|
|
7
|
-
const closer = /^[-+*=~^_,.;:!?]*(?=[\\"`|\[\](){}<>]|$)/;
|
|
7
|
+
const closer = /^[-+*=~^_,.;:!?]*(?=[\\"`|\[\](){}<>]|[^\x21-\x7E]|$)/;
|
|
8
8
|
|
|
9
9
|
export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'https://'], rewrite(
|
|
10
10
|
open(
|
|
11
11
|
/^https?:\/\/(?=[\x21-\x7E])/,
|
|
12
|
-
|
|
12
|
+
precedence(1, some(union([
|
|
13
13
|
verify(bracket, ns => ns.length > 0),
|
|
14
14
|
some(unescsource, closer),
|
|
15
|
-
])))
|
|
15
|
+
]), undefined, [[/^[^\x21-\x7E]/, 3]])),
|
|
16
|
+
false,
|
|
17
|
+
[3 | Backtrack.autolink]),
|
|
16
18
|
union([
|
|
17
19
|
constraint(State.autolink, false, state(State.autolink, convert(
|
|
18
20
|
url => `{ ${url} }`,
|
|
@@ -34,15 +36,17 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => open(
|
|
|
34
36
|
false))),
|
|
35
37
|
({ source }) => [[source], ''],
|
|
36
38
|
]),
|
|
37
|
-
]))
|
|
39
|
+
])),
|
|
40
|
+
false,
|
|
41
|
+
[3 | Backtrack.autolink]));
|
|
38
42
|
|
|
39
43
|
const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => union([
|
|
40
44
|
surround(str('('), recursion(Recursion.terminal, some(union([bracket, unescsource]), ')')), str(')'), true,
|
|
41
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
45
|
+
undefined, () => [[], ''], [3 | Backtrack.autolink]),
|
|
42
46
|
surround(str('['), recursion(Recursion.terminal, some(union([bracket, unescsource]), ']')), str(']'), true,
|
|
43
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
47
|
+
undefined, () => [[], ''], [3 | Backtrack.autolink]),
|
|
44
48
|
surround(str('{'), recursion(Recursion.terminal, some(union([bracket, unescsource]), '}')), str('}'), true,
|
|
45
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
49
|
+
undefined, () => [[], ''], [3 | Backtrack.autolink]),
|
|
46
50
|
surround(str('"'), precedence(2, recursion(Recursion.terminal, some(unescsource, '"'))), str('"'), true,
|
|
47
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
51
|
+
undefined, () => [[], ''], [3 | Backtrack.autolink]),
|
|
48
52
|
]));
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { CodeParser } from '../inline';
|
|
2
|
-
import { match } from '../../combinator';
|
|
2
|
+
import { validate, getBacktrack, setBacktrack, match } from '../../combinator';
|
|
3
|
+
import { Backtrack } from '../context';
|
|
3
4
|
import { html } from 'typed-dom/dom';
|
|
4
5
|
|
|
5
|
-
export const code: CodeParser =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export const code: CodeParser = validate(
|
|
7
|
+
({ source, context }) =>
|
|
8
|
+
source[0] === '`' &&
|
|
9
|
+
!getBacktrack(context, [1 | Backtrack.bracket], source, source.length - 1),
|
|
10
|
+
match(
|
|
11
|
+
/^(`+)(?!`)([^\n]*?)(?:((?<!`)\1(?!`))|$|\n)/,
|
|
12
|
+
([whole, , body, closer]) => ({ source, context }) =>
|
|
13
|
+
closer
|
|
14
|
+
? [[html('code', { 'data-src': whole }, format(body))], source.slice(whole.length)]
|
|
15
|
+
: void setBacktrack(context, [2 | Backtrack.bracket], source.length),
|
|
16
|
+
true));
|
|
10
17
|
|
|
11
18
|
function format(text: string): string {
|
|
12
19
|
assert(text.length > 0);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExtensionParser } from '../../inline';
|
|
2
|
-
import { State, Backtrack,
|
|
2
|
+
import { State, Backtrack, Command } from '../../context';
|
|
3
3
|
import { eval } from '../../../combinator/data/parser';
|
|
4
4
|
import { union, inits, some, precedence, state, constraint, validate, surround, lazy, fmap } from '../../../combinator';
|
|
5
5
|
import { inline } from '../../inline';
|
|
@@ -19,16 +19,16 @@ export const index: IndexParser = lazy(() => constraint(State.index, false, fmap
|
|
|
19
19
|
some(inits([
|
|
20
20
|
inline,
|
|
21
21
|
signature,
|
|
22
|
-
]), ']', [['
|
|
22
|
+
]), ']', [[']', 1]])))),
|
|
23
23
|
']',
|
|
24
24
|
false,
|
|
25
|
-
([, ns], rest) =>
|
|
25
|
+
([, ns], rest, context) =>
|
|
26
|
+
context.linebreak === undefined &&
|
|
26
27
|
trimBlankNodeEnd(ns).length > 0
|
|
27
28
|
? [[html('a', { 'data-index': dataindex(ns) }, defrag(ns))], rest]
|
|
28
29
|
: undefined,
|
|
29
30
|
undefined,
|
|
30
|
-
[3 | Backtrack.
|
|
31
|
-
Backtrack.bracket | BacktrackState.nobreak)),
|
|
31
|
+
[3 | Backtrack.bracket])),
|
|
32
32
|
([el]: [HTMLAnchorElement]) => [
|
|
33
33
|
define(el,
|
|
34
34
|
{
|
|
@@ -13,7 +13,7 @@ export const segment: ExtensionParser.LabelParser.SegmentParser = clear(union([
|
|
|
13
13
|
|
|
14
14
|
export const label: ExtensionParser.LabelParser = constraint(State.label, false, fmap(
|
|
15
15
|
union([
|
|
16
|
-
surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.
|
|
16
|
+
surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.bracket, 1]),
|
|
17
17
|
body,
|
|
18
18
|
]),
|
|
19
19
|
([text]) => [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HTMLParser } from '../inline';
|
|
2
|
-
import { Recursion } from '../context';
|
|
2
|
+
import { Recursion, Backtrack } from '../context';
|
|
3
3
|
import { union, subsequence, some, recursion, precedence, validate, focus, surround, open, match, lazy } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
5
|
import { str } from '../source';
|
|
@@ -30,13 +30,17 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
|
|
|
30
30
|
some(union([attribute])),
|
|
31
31
|
str(/^[^\S\n]*>/), true,
|
|
32
32
|
([as, bs = [], cs], rest) =>
|
|
33
|
-
[[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest]
|
|
33
|
+
[[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
|
|
34
|
+
undefined,
|
|
35
|
+
[3 | Backtrack.bracket]),
|
|
34
36
|
match(
|
|
35
37
|
new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`),
|
|
36
38
|
memoize(
|
|
37
39
|
([, tag]) =>
|
|
38
40
|
surround<HTMLParser.TagParser, string>(
|
|
39
|
-
surround(
|
|
41
|
+
surround(
|
|
42
|
+
str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
|
|
43
|
+
undefined, undefined, [3 | Backtrack.bracket]),
|
|
40
44
|
precedence(3, recursion(Recursion.inline,
|
|
41
45
|
subsequence([
|
|
42
46
|
focus(/^[^\S\n]*\n/, some(inline)),
|
|
@@ -54,7 +58,9 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
|
|
|
54
58
|
memoize(
|
|
55
59
|
([, tag]) =>
|
|
56
60
|
surround<HTMLParser.TagParser, string>(
|
|
57
|
-
surround(
|
|
61
|
+
surround(
|
|
62
|
+
str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
|
|
63
|
+
undefined, undefined, [3 | Backtrack.bracket]),
|
|
58
64
|
precedence(3, recursion(Recursion.inline,
|
|
59
65
|
subsequence([
|
|
60
66
|
focus(/^[^\S\n]*\n/, some(inline)),
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { MarkdownParser } from '../../../markdown';
|
|
2
2
|
import { LinkParser } from '../inline';
|
|
3
|
-
import { State, Backtrack,
|
|
4
|
-
import { union, inits, tails, sequence, some, creation, precedence, state, constraint, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
|
|
3
|
+
import { State, Backtrack, Command } from '../context';
|
|
4
|
+
import { union, inits, tails, sequence, subsequence, some, creation, precedence, state, constraint, validate, surround, open, setBacktrack, dup, reverse, lazy, fmap, bind } from '../../combinator';
|
|
5
5
|
import { inline, media, shortmedia } from '../inline';
|
|
6
6
|
import { attributes } from './html';
|
|
7
7
|
import { linebreak, unescsource, str } from '../source';
|
|
8
8
|
import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
|
|
9
9
|
import { invalid, stringify } from '../util';
|
|
10
10
|
import { ReadonlyURL } from 'spica/url';
|
|
11
|
+
import { push } from 'spica/array';
|
|
11
12
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
12
13
|
|
|
13
14
|
const optspec = {
|
|
@@ -17,22 +18,36 @@ Object.setPrototypeOf(optspec, null);
|
|
|
17
18
|
|
|
18
19
|
export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.link, false, creation(10,
|
|
19
20
|
precedence(1, state(State.linkers | State.media,
|
|
20
|
-
bind(
|
|
21
|
+
bind(subsequence([
|
|
21
22
|
dup(surround(
|
|
22
23
|
'[',
|
|
23
|
-
trimBlankStart(some(union([inline]), ']', [['
|
|
24
|
+
trimBlankStart(some(union([inline]), ']', [[']', 1]])),
|
|
24
25
|
']',
|
|
25
|
-
true,
|
|
26
|
-
[
|
|
27
|
-
|
|
26
|
+
true,
|
|
27
|
+
([, ns = []], rest, context) =>
|
|
28
|
+
context.linebreak === undefined
|
|
29
|
+
? [push(ns, [Command.Escape]), rest]
|
|
30
|
+
: undefined,
|
|
31
|
+
undefined,
|
|
32
|
+
[3 | Backtrack.link, 3 | Backtrack.bracket])),
|
|
28
33
|
dup(surround(
|
|
29
34
|
/^{(?![{}])/,
|
|
30
35
|
inits([uri, some(option)]),
|
|
31
36
|
/^[^\S\n]*}/,
|
|
32
37
|
false, undefined, undefined,
|
|
33
38
|
[3 | Backtrack.link])),
|
|
34
|
-
])
|
|
35
|
-
([
|
|
39
|
+
]),
|
|
40
|
+
([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
|
|
41
|
+
if (content.at(-1) === Command.Escape) {
|
|
42
|
+
content.pop();
|
|
43
|
+
if (params === undefined) {
|
|
44
|
+
return void setBacktrack(context, [2 | Backtrack.link], context.recent!.reduce((a, b) => a + b.length, 0));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
params = content as string[];
|
|
49
|
+
content = [];
|
|
50
|
+
}
|
|
36
51
|
assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
|
|
37
52
|
assert(content[0] !== '');
|
|
38
53
|
if (content.length !== 0 && trimBlankNodeEnd(content).length === 0) return;
|
|
@@ -27,10 +27,15 @@ export const media: MediaParser = lazy(() => constraint(State.media, false, vali
|
|
|
27
27
|
unsafehtmlentity,
|
|
28
28
|
bracket,
|
|
29
29
|
txt,
|
|
30
|
-
]), ']'
|
|
30
|
+
]), ']')),
|
|
31
31
|
']',
|
|
32
|
-
true,
|
|
33
|
-
[
|
|
32
|
+
true,
|
|
33
|
+
([, ns = []], rest, context) =>
|
|
34
|
+
context.linebreak === undefined
|
|
35
|
+
? [ns, rest]
|
|
36
|
+
: undefined,
|
|
37
|
+
undefined,
|
|
38
|
+
[3 | Backtrack.escbracket])),
|
|
34
39
|
dup(surround(
|
|
35
40
|
/^{(?![{}])/,
|
|
36
41
|
inits([uri, some(option)]),
|
|
@@ -77,13 +82,13 @@ export const linemedia: MediaParser.LineMediaParser = surround(
|
|
|
77
82
|
|
|
78
83
|
const bracket: MediaParser.TextParser.BracketParser = lazy(() => recursion(Recursion.terminal, union([
|
|
79
84
|
surround(str('('), some(union([unsafehtmlentity, bracket, txt]), ')'), str(')'), true,
|
|
80
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
85
|
+
undefined, () => [[], ''], [3 | Backtrack.escbracket]),
|
|
81
86
|
surround(str('['), some(union([unsafehtmlentity, bracket, txt]), ']'), str(']'), true,
|
|
82
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
87
|
+
undefined, () => [[], ''], [3 | Backtrack.escbracket]),
|
|
83
88
|
surround(str('{'), some(union([unsafehtmlentity, bracket, txt]), '}'), str('}'), true,
|
|
84
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
89
|
+
undefined, () => [[], ''], [3 | Backtrack.escbracket]),
|
|
85
90
|
surround(str('"'), precedence(2, some(union([unsafehtmlentity, txt]), '"')), str('"'), true,
|
|
86
|
-
undefined, () => [[], ''], [3 | Backtrack.
|
|
91
|
+
undefined, () => [[], ''], [3 | Backtrack.escbracket]),
|
|
87
92
|
])));
|
|
88
93
|
|
|
89
94
|
const option: MediaParser.ParameterParser.OptionParser = lazy(() => union([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReferenceParser } from '../inline';
|
|
2
|
-
import { State, Backtrack,
|
|
3
|
-
import { union, subsequence, some, precedence, state, constraint, surround, lazy } from '../../combinator';
|
|
2
|
+
import { State, Backtrack, Command } from '../context';
|
|
3
|
+
import { union, subsequence, some, precedence, state, constraint, surround, setBacktrack, lazy } from '../../combinator';
|
|
4
4
|
import { inline } from '../inline';
|
|
5
5
|
import { str } from '../source';
|
|
6
6
|
import { blank, trimBlankStart, trimBlankNodeEnd } from '../visibility';
|
|
@@ -13,20 +13,24 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
|
|
|
13
13
|
precedence(1, state(State.annotation | State.reference | State.media,
|
|
14
14
|
subsequence([
|
|
15
15
|
abbr,
|
|
16
|
-
trimBlankStart(some(inline, ']', [['
|
|
16
|
+
trimBlankStart(some(inline, ']', [[']', 1]])),
|
|
17
17
|
]))),
|
|
18
18
|
']]',
|
|
19
19
|
false,
|
|
20
|
-
([, ns], rest) =>
|
|
20
|
+
([, ns], rest, context) =>
|
|
21
|
+
context.linebreak === undefined &&
|
|
21
22
|
trimBlankNodeEnd(ns).length > 0
|
|
22
23
|
? [[html('sup', attributes(ns), [html('span', defrag(ns))])], rest]
|
|
23
24
|
: undefined,
|
|
24
|
-
([as, bs], rest,
|
|
25
|
-
|
|
25
|
+
([as, bs], rest, context) => {
|
|
26
|
+
if (rest[0] !== ']') {
|
|
27
|
+
setBacktrack(context, [2 | Backtrack.bracket], context.recent!.reduce((a, b) => a + b.length, 0), 2);
|
|
28
|
+
}
|
|
29
|
+
return context.state! & State.annotation
|
|
26
30
|
? [unshift(as, bs), rest]
|
|
27
|
-
: undefined
|
|
28
|
-
|
|
29
|
-
Backtrack.
|
|
31
|
+
: undefined;
|
|
32
|
+
},
|
|
33
|
+
[3 | Backtrack.doublebracket, 1 | Backtrack.bracket])));
|
|
30
34
|
|
|
31
35
|
// Chicago-Style
|
|
32
36
|
const abbr: ReferenceParser.AbbrParser = surround(
|
|
@@ -34,14 +38,14 @@ const abbr: ReferenceParser.AbbrParser = surround(
|
|
|
34
38
|
union([str(/^(?=[A-Z])(?:[0-9A-Za-z]'?|(?:[-.:]|\.?\??,? ?)(?!['\-.:?, ]))+/)]),
|
|
35
39
|
/^\|?(?=]])|^\|[^\S\n]*/,
|
|
36
40
|
true,
|
|
37
|
-
([, ns], rest) => ns ? [[
|
|
41
|
+
([, ns], rest) => ns ? [[Command.Escape, ns[0].trimEnd()], rest.replace(blank.start, '')] : [[''], `^${rest}`],
|
|
38
42
|
([, , rest]) => [[''], `^${rest}`]);
|
|
39
43
|
|
|
40
44
|
function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
|
|
41
45
|
switch (ns[0]) {
|
|
42
46
|
case '':
|
|
43
47
|
return { class: 'invalid', ...invalid('reference', 'syntax', 'Invalid abbreviation') };
|
|
44
|
-
case
|
|
48
|
+
case Command.Escape:
|
|
45
49
|
const abbr = ns[1] as string;
|
|
46
50
|
ns[0] = ns[1] = '';
|
|
47
51
|
return { class: 'reference', 'data-abbr': abbr };
|