securemark 0.296.0 → 0.296.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 +9 -1
- package/dist/index.js +196 -187
- package/package.json +1 -1
- package/src/combinator/control/constraint/block.ts +2 -2
- package/src/combinator/control/constraint/contract.ts +4 -3
- package/src/combinator/control/constraint/line.ts +5 -5
- package/src/combinator/control/manipulation/fence.ts +4 -4
- package/src/combinator/control/manipulation/scope.ts +1 -1
- package/src/combinator/control/manipulation/surround.ts +31 -15
- package/src/combinator/data/delimiter.ts +82 -6
- package/src/combinator/data/parser/context.ts +1 -34
- package/src/parser/api/normalize.ts +5 -1
- package/src/parser/block/reply/cite.ts +1 -1
- package/src/parser/block/reply/quote.ts +1 -1
- package/src/parser/block/reply.ts +1 -1
- package/src/parser/inline/annotation.ts +3 -3
- package/src/parser/inline/deletion.ts +1 -1
- package/src/parser/inline/emphasis.ts +4 -4
- package/src/parser/inline/emstrong.test.ts +1 -0
- package/src/parser/inline/emstrong.ts +3 -3
- package/src/parser/inline/extension/index.ts +2 -3
- package/src/parser/inline/extension/placeholder.ts +2 -2
- package/src/parser/inline/html.ts +3 -3
- package/src/parser/inline/htmlentity.ts +2 -2
- package/src/parser/inline/insertion.ts +1 -1
- package/src/parser/inline/italic.test.ts +1 -0
- package/src/parser/inline/italic.ts +2 -2
- package/src/parser/inline/link.test.ts +0 -1
- package/src/parser/inline/link.ts +3 -3
- package/src/parser/inline/mark.test.ts +1 -0
- package/src/parser/inline/mark.ts +4 -4
- package/src/parser/inline/media.test.ts +0 -1
- package/src/parser/inline/media.ts +1 -1
- package/src/parser/inline/reference.ts +3 -3
- package/src/parser/inline/ruby.test.ts +5 -0
- package/src/parser/inline/ruby.ts +2 -2
- package/src/parser/inline/strong.ts +4 -4
- package/src/parser/node.ts +4 -4
- package/src/parser/source/escapable.ts +2 -1
- package/src/parser/source/str.ts +4 -4
- package/src/parser/source/text.ts +1 -1
- package/src/parser/source/unescapable.ts +2 -1
- package/src/parser/util.ts +7 -4
- package/src/parser/visibility.ts +41 -89
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser, failsafe } from '../../data/parser';
|
|
2
|
-
import {
|
|
2
|
+
import { isEmptyline } from './line';
|
|
3
3
|
|
|
4
4
|
export function block<P extends Parser>(parser: P, separation?: boolean): P;
|
|
5
5
|
export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
|
|
@@ -10,7 +10,7 @@ export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
|
|
|
10
10
|
if (position === source.length) return;
|
|
11
11
|
const result = parser(input);
|
|
12
12
|
if (result === undefined) return;
|
|
13
|
-
if (separation && !
|
|
13
|
+
if (separation && !isEmptyline(source, context.position)) return;
|
|
14
14
|
assert(context.position === source.length || source[context.position - 1] === '\n');
|
|
15
15
|
return context.position === source.length || source[context.position - 1] === '\n'
|
|
16
16
|
? result
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Parser, Input, List, Node, Context } from '../../data/parser';
|
|
2
|
-
import {
|
|
2
|
+
import { tester } from '../../data/delimiter';
|
|
3
|
+
import { bind } from '../monad/bind';
|
|
3
4
|
|
|
4
5
|
//export function contract<P extends Parser>(patterns: string | RegExp | (string | RegExp)[], parser: P, cond: (nodes: readonly Data<P>[], rest: string) => boolean): P;
|
|
5
6
|
//export function contract<N>(patterns: string | RegExp | (string | RegExp)[], parser: Parser<N>, cond: (nodes: readonly N[], rest: string) => boolean): Parser<N> {
|
|
@@ -10,8 +11,8 @@ export function validate<P extends Parser>(pattern: string | RegExp, parser: P):
|
|
|
10
11
|
export function validate<P extends Parser>(cond: ((input: Input<Parser.Context<P>>) => boolean), parser: P): P;
|
|
11
12
|
export function validate<N>(pattern: string | RegExp | ((input: Input<Context>) => boolean), parser: Parser<N>): Parser<N> {
|
|
12
13
|
if (typeof pattern === 'function') return guard(pattern, parser);
|
|
13
|
-
const
|
|
14
|
-
return input =>
|
|
14
|
+
const test = tester(pattern, false);
|
|
15
|
+
return input => test(input) && parser(input);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function guard<P extends Parser>(f: (input: Input<Parser.Context<P>>) => boolean, parser: P): P;
|
|
@@ -14,7 +14,7 @@ export function line<N>(parser: Parser<N>): Parser<N> {
|
|
|
14
14
|
context.source = source;
|
|
15
15
|
context.offset -= position;
|
|
16
16
|
if (result === undefined) return;
|
|
17
|
-
if (context.position < position + line.length && !
|
|
17
|
+
if (context.position < position + line.length && !isEmptyline(source, context.position)) return;
|
|
18
18
|
context.position = position + line.length;
|
|
19
19
|
return result;
|
|
20
20
|
});
|
|
@@ -27,10 +27,10 @@ export function firstline(source: string, position: number): string {
|
|
|
27
27
|
: source.slice(position, i + 1);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
export function
|
|
32
|
-
|
|
30
|
+
const emptyline = /[^\S\n]*(?:$|\n)/y;
|
|
31
|
+
export function isEmptyline(source: string, position: number): boolean {
|
|
32
|
+
emptyline.lastIndex = position;
|
|
33
33
|
return source.length === position
|
|
34
34
|
|| source[position] === '\n'
|
|
35
|
-
||
|
|
35
|
+
|| emptyline.test(source);
|
|
36
36
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Parser, List, Node, Context, failsafe } from '../../data/parser';
|
|
2
2
|
import { consume } from '../../../combinator';
|
|
3
|
-
import { firstline,
|
|
3
|
+
import { firstline, isEmptyline } from '../constraint/line';
|
|
4
4
|
import { push } from 'spica/array';
|
|
5
5
|
|
|
6
6
|
export function fence<C extends Context, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
|
|
@@ -20,20 +20,20 @@ export function fence<C extends Context, D extends Parser<unknown, C>[]>(opener:
|
|
|
20
20
|
context.position += matches[0].length;
|
|
21
21
|
// Prevent annoying parsing in editing.
|
|
22
22
|
const secondline = firstline(source, context.position);
|
|
23
|
-
if (
|
|
23
|
+
if (isEmptyline(secondline, 0) && firstline(source, context.position + secondline.length).trimEnd() !== delim) return;
|
|
24
24
|
let block = '';
|
|
25
25
|
let closer = '';
|
|
26
26
|
let overflow = '';
|
|
27
27
|
for (let count = 1; ; ++count) {
|
|
28
28
|
if (context.position === source.length) break;
|
|
29
29
|
const line = firstline(source, context.position);
|
|
30
|
-
if ((closer || count > limit + 1) &&
|
|
30
|
+
if ((closer || count > limit + 1) && isEmptyline(line, 0)) break;
|
|
31
31
|
if(closer) {
|
|
32
32
|
overflow += line;
|
|
33
33
|
}
|
|
34
34
|
if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
|
|
35
35
|
closer = line;
|
|
36
|
-
if (
|
|
36
|
+
if (isEmptyline(source, context.position + line.length)) {
|
|
37
37
|
context.position += line.length;
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parser, input, failsafe } from '../../data/parser';
|
|
2
|
-
import { matcher } from '
|
|
2
|
+
import { matcher } from '../../data/delimiter';
|
|
3
3
|
|
|
4
4
|
export function focus<P extends Parser>(scope: string | RegExp, parser: P, slice?: boolean): P;
|
|
5
5
|
export function focus<N>(scope: string | RegExp, parser: Parser<N>, slice = true): Parser<N> {
|
|
@@ -1,50 +1,64 @@
|
|
|
1
1
|
import { Parser, Result, List, Node, Context, failsafe } from '../../data/parser';
|
|
2
|
-
import {
|
|
2
|
+
import { tester } from '../../data/delimiter';
|
|
3
3
|
|
|
4
4
|
export function surround<P extends Parser, S = string>(
|
|
5
|
-
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
5
|
+
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
6
|
+
parser: Parser.IntermediateParser<P>,
|
|
7
|
+
closer: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
6
8
|
optional?: false,
|
|
7
9
|
backtracks?: readonly number[],
|
|
8
10
|
f?: (rss: [List<Node<S>>, List<Node<Parser.SubNode<P>>>, List<Node<S>>], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
9
11
|
g?: (rss: [List<Node<S>>, List<Node<Parser.SubNode<P>>> | undefined], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
10
12
|
): P;
|
|
11
13
|
export function surround<P extends Parser, S = string>(
|
|
12
|
-
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
14
|
+
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
15
|
+
parser: Parser.IntermediateParser<P>,
|
|
16
|
+
closer: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
13
17
|
optional?: boolean,
|
|
14
18
|
backtracks?: readonly number[],
|
|
15
19
|
f?: (rss: [List<Node<S>>, List<Node<Parser.SubNode<P>>> | undefined, List<Node<S>>], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
16
20
|
g?: (rss: [List<Node<S>>, List<Node<Parser.SubNode<P>>> | undefined], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
17
21
|
): P;
|
|
18
22
|
export function surround<P extends Parser, S = string>(
|
|
19
|
-
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
23
|
+
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
24
|
+
parser: P,
|
|
25
|
+
closer: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
20
26
|
optional?: false,
|
|
21
27
|
backtracks?: readonly number[],
|
|
22
28
|
f?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>>, List<Node<S>>], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
23
29
|
g?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>> | undefined], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
24
30
|
): P;
|
|
25
31
|
export function surround<P extends Parser, S = string>(
|
|
26
|
-
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
32
|
+
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
33
|
+
parser: P,
|
|
34
|
+
closer: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
27
35
|
optional?: boolean,
|
|
28
36
|
backtracks?: readonly number[],
|
|
29
37
|
f?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>> | undefined, List<Node<S>>], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
30
38
|
g?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>> | undefined], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
31
39
|
): P;
|
|
32
40
|
export function surround<P extends Parser<string>, S = string>(
|
|
33
|
-
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
41
|
+
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
42
|
+
parser: string | RegExp | P,
|
|
43
|
+
closer: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
34
44
|
optional?: false,
|
|
35
45
|
backtracks?: readonly number[],
|
|
36
46
|
f?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>>, List<Node<S>>], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
37
47
|
g?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>> | undefined], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
38
48
|
): P;
|
|
39
49
|
export function surround<P extends Parser<string>, S = string>(
|
|
40
|
-
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
50
|
+
opener: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
51
|
+
parser: string | RegExp | P,
|
|
52
|
+
closer: string | RegExp | Parser<S, Parser.Context<P>>,
|
|
41
53
|
optional?: boolean,
|
|
42
54
|
backtracks?: readonly number[],
|
|
43
55
|
f?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>> | undefined, List<Node<S>>], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
44
56
|
g?: (rss: [List<Node<S>>, List<Node<Parser.Node<P>>> | undefined], context: Parser.Context<P>) => Result<Parser.Node<P>, Parser.Context<P>, Parser.SubParsers<P>>,
|
|
45
57
|
): P;
|
|
46
58
|
export function surround<N>(
|
|
47
|
-
opener: string | RegExp | Parser<N>,
|
|
59
|
+
opener: string | RegExp | Parser<N>,
|
|
60
|
+
parser: string | RegExp | Parser<N>,
|
|
61
|
+
closer: string | RegExp | Parser<N>,
|
|
48
62
|
optional: boolean = false,
|
|
49
63
|
backtracks: readonly number[] = [],
|
|
50
64
|
f?: (rss: [List<Node<N>>, List<Node<N>>, List<Node<N>>], context: Context) => Result<N>,
|
|
@@ -53,19 +67,19 @@ export function surround<N>(
|
|
|
53
67
|
switch (typeof opener) {
|
|
54
68
|
case 'string':
|
|
55
69
|
case 'object':
|
|
56
|
-
opener =
|
|
70
|
+
opener = tester(opener, true);
|
|
57
71
|
}
|
|
58
72
|
assert(opener);
|
|
59
73
|
switch (typeof parser) {
|
|
60
74
|
case 'string':
|
|
61
75
|
case 'object':
|
|
62
|
-
parser =
|
|
76
|
+
parser = tester(parser, true);
|
|
63
77
|
}
|
|
64
78
|
assert(parser);
|
|
65
79
|
switch (typeof closer) {
|
|
66
80
|
case 'string':
|
|
67
81
|
case 'object':
|
|
68
|
-
closer =
|
|
82
|
+
closer = tester(closer, true);
|
|
69
83
|
}
|
|
70
84
|
assert(closer);
|
|
71
85
|
const [blen, rbs, wbs] = reduce(backtracks);
|
|
@@ -76,7 +90,7 @@ export function surround<N>(
|
|
|
76
90
|
const { linebreak } = context;
|
|
77
91
|
context.linebreak = 0;
|
|
78
92
|
const nodesO = opener(input);
|
|
79
|
-
if (
|
|
93
|
+
if (nodesO === undefined) {
|
|
80
94
|
return void revert(context, linebreak);
|
|
81
95
|
}
|
|
82
96
|
if (rbs && isBacktrack(context, rbs, position, blen)) {
|
|
@@ -84,14 +98,14 @@ export function surround<N>(
|
|
|
84
98
|
}
|
|
85
99
|
const nodesM = context.position < source.length ? parser(input) : undefined;
|
|
86
100
|
context.range = context.position - position;
|
|
87
|
-
if (
|
|
101
|
+
if (nodesM === undefined && !optional) {
|
|
88
102
|
wbs && setBacktrack(context, wbs, position);
|
|
89
103
|
const result = g?.([nodesO, nodesM], context);
|
|
90
104
|
return result || void revert(context, linebreak);
|
|
91
105
|
}
|
|
92
106
|
const nodesC = nodesM || optional ? closer(input) : undefined;
|
|
93
107
|
context.range = context.position - position;
|
|
94
|
-
if (
|
|
108
|
+
if (nodesC === undefined) {
|
|
95
109
|
wbs && setBacktrack(context, wbs, position);
|
|
96
110
|
const result = g?.([nodesO, nodesM], context);
|
|
97
111
|
return result || void revert(context, linebreak);
|
|
@@ -102,7 +116,9 @@ export function surround<N>(
|
|
|
102
116
|
context.range = context.position - position;
|
|
103
117
|
const result = f
|
|
104
118
|
? f([nodesO, nodesM!, nodesC], context)
|
|
105
|
-
:
|
|
119
|
+
: nodesM
|
|
120
|
+
? nodesO.import(nodesM).import(nodesC)
|
|
121
|
+
: nodesO.import(nodesC);
|
|
106
122
|
if (result) {
|
|
107
123
|
context.linebreak ||= linebreak;
|
|
108
124
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Input, Context } from './parser';
|
|
2
|
-
import {
|
|
1
|
+
import { Parser, Input, List, Node, Context } from './parser';
|
|
2
|
+
import { consume } from './parser/context';
|
|
3
3
|
|
|
4
4
|
interface Delimiter {
|
|
5
5
|
readonly memory: Delimiter[];
|
|
@@ -33,11 +33,11 @@ export class Delimiters {
|
|
|
33
33
|
return () => undefined;
|
|
34
34
|
case 'string':
|
|
35
35
|
case 'object':
|
|
36
|
-
const
|
|
37
|
-
const verify = after ?
|
|
36
|
+
const test = tester(pattern, false);
|
|
37
|
+
const verify = after ? tester(after, false) : undefined;
|
|
38
38
|
return verify
|
|
39
|
-
? input =>
|
|
40
|
-
: input =>
|
|
39
|
+
? input => test(input) !== undefined && verify(input) !== undefined || undefined
|
|
40
|
+
: input => test(input) !== undefined || undefined;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
private readonly tree: Record<number, Delimiter[]> = {};
|
|
@@ -150,3 +150,79 @@ export class Delimiters {
|
|
|
150
150
|
return false;
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
export function matcher(pattern: string | RegExp, advance: boolean, after?: Parser<string>): Parser<string> {
|
|
155
|
+
assert(pattern instanceof RegExp ? !pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^') : true);
|
|
156
|
+
const count = typeof pattern === 'object'
|
|
157
|
+
? /[^^\\*+][*+]/.test(pattern.source)
|
|
158
|
+
: false;
|
|
159
|
+
switch (typeof pattern) {
|
|
160
|
+
case 'string':
|
|
161
|
+
if (pattern === '') return () => new List([new Node(pattern)]);
|
|
162
|
+
return input => {
|
|
163
|
+
const { context } = input;
|
|
164
|
+
const { source, position } = context;
|
|
165
|
+
if (!source.startsWith(pattern, position)) return;
|
|
166
|
+
if (advance) {
|
|
167
|
+
context.position += pattern.length;
|
|
168
|
+
}
|
|
169
|
+
const next = after?.(input);
|
|
170
|
+
return after
|
|
171
|
+
? next && new List([new Node(pattern)]).import(next)
|
|
172
|
+
: new List([new Node(pattern)]);
|
|
173
|
+
};
|
|
174
|
+
case 'object':
|
|
175
|
+
assert(pattern.sticky);
|
|
176
|
+
return input => {
|
|
177
|
+
const { context } = input;
|
|
178
|
+
const { source, position } = context;
|
|
179
|
+
pattern.lastIndex = position;
|
|
180
|
+
if (!pattern.test(source)) return;
|
|
181
|
+
const src = source.slice(position, pattern.lastIndex);
|
|
182
|
+
count && consume(src.length, context);
|
|
183
|
+
if (advance) {
|
|
184
|
+
context.position += src.length;
|
|
185
|
+
}
|
|
186
|
+
const next = after?.(input);
|
|
187
|
+
return after
|
|
188
|
+
? next && new List([new Node(src)]).import(next)
|
|
189
|
+
: new List([new Node(src)]);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function tester(pattern: string | RegExp, advance: boolean, after?: Parser<unknown>): Parser<never> {
|
|
195
|
+
assert(pattern instanceof RegExp ? !pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^') : true);
|
|
196
|
+
const count = typeof pattern === 'object'
|
|
197
|
+
? /[^^\\*+][*+]/.test(pattern.source)
|
|
198
|
+
: false;
|
|
199
|
+
switch (typeof pattern) {
|
|
200
|
+
case 'string':
|
|
201
|
+
if (pattern === '') return () => new List();
|
|
202
|
+
return input => {
|
|
203
|
+
const { context } = input;
|
|
204
|
+
const { source, position } = context;
|
|
205
|
+
if (!source.startsWith(pattern, position)) return;
|
|
206
|
+
if (advance) {
|
|
207
|
+
context.position += pattern.length;
|
|
208
|
+
}
|
|
209
|
+
if (after && after(input) === undefined) return;
|
|
210
|
+
return new List();
|
|
211
|
+
};
|
|
212
|
+
case 'object':
|
|
213
|
+
assert(pattern.sticky);
|
|
214
|
+
return input => {
|
|
215
|
+
const { context } = input;
|
|
216
|
+
const { source, position } = context;
|
|
217
|
+
pattern.lastIndex = position;
|
|
218
|
+
if (!pattern.test(source)) return;
|
|
219
|
+
const len = pattern.lastIndex - position;
|
|
220
|
+
count && consume(len, context);
|
|
221
|
+
if (advance) {
|
|
222
|
+
context.position += len;
|
|
223
|
+
}
|
|
224
|
+
if (after && after(input) === undefined) return;
|
|
225
|
+
return new List();
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Parser, Result,
|
|
1
|
+
import { Parser, Result, Context, Options } from '../../data/parser';
|
|
2
2
|
import { min } from 'spica/alias';
|
|
3
3
|
import { clone } from 'spica/assign';
|
|
4
4
|
|
|
@@ -158,36 +158,3 @@ export function constraint<N>(state: number, positive: boolean | Parser<N>, pars
|
|
|
158
158
|
: undefined;
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
export function matcher(pattern: string | RegExp, advance: boolean, verify?: (source: string, position: number, range: number) => boolean): Parser<string> {
|
|
163
|
-
assert(pattern instanceof RegExp ? !pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^') : true);
|
|
164
|
-
const count = typeof pattern === 'object'
|
|
165
|
-
? /[^^\\*+][*+]/.test(pattern.source)
|
|
166
|
-
: false;
|
|
167
|
-
switch (typeof pattern) {
|
|
168
|
-
case 'string':
|
|
169
|
-
return ({ context }) => {
|
|
170
|
-
const { source, position } = context;
|
|
171
|
-
if (!source.startsWith(pattern, position)) return;
|
|
172
|
-
if (verify?.(source, position, pattern.length) === false) return;
|
|
173
|
-
if (advance) {
|
|
174
|
-
context.position += pattern.length;
|
|
175
|
-
}
|
|
176
|
-
return new List([new Node(pattern)]);
|
|
177
|
-
};
|
|
178
|
-
case 'object':
|
|
179
|
-
assert(pattern.sticky);
|
|
180
|
-
return ({ context }) => {
|
|
181
|
-
const { source, position } = context;
|
|
182
|
-
pattern.lastIndex = position;
|
|
183
|
-
if (!pattern.test(source)) return;
|
|
184
|
-
const src = source.slice(position, pattern.lastIndex);
|
|
185
|
-
count && consume(src.length, context);
|
|
186
|
-
if (verify?.(source, position, src.length) === false) return;
|
|
187
|
-
if (advance) {
|
|
188
|
-
context.position += src.length;
|
|
189
|
-
}
|
|
190
|
-
return new List([new Node(src)]);
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
@@ -24,7 +24,7 @@ function sanitize(source: string): string {
|
|
|
24
24
|
|
|
25
25
|
// https://dev.w3.org/html5/html-author/charref
|
|
26
26
|
// https://en.wikipedia.org/wiki/Whitespace_character
|
|
27
|
-
|
|
27
|
+
const invisibleHTMLEntityNames = [
|
|
28
28
|
'Tab',
|
|
29
29
|
'NewLine',
|
|
30
30
|
'NonBreakingSpace',
|
|
@@ -63,6 +63,10 @@ const parser = (el => (entity: string): string => {
|
|
|
63
63
|
el.innerHTML = entity;
|
|
64
64
|
return el.textContent!;
|
|
65
65
|
})(html('span'));
|
|
66
|
+
export const invisibleBlankHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames
|
|
67
|
+
.filter(name => parser(`&${name};`).trimStart() === '');
|
|
68
|
+
export const invisibleGraphHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames
|
|
69
|
+
.filter(name => parser(`&${name};`).trimStart() !== '');
|
|
66
70
|
const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
|
|
67
71
|
'Tab',
|
|
68
72
|
'NewLine',
|
|
@@ -22,7 +22,7 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
|
|
|
22
22
|
unescsource,
|
|
23
23
|
])))),
|
|
24
24
|
(ns, { source, position }) => new List([
|
|
25
|
-
new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.
|
|
25
|
+
new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.blank),
|
|
26
26
|
new Node(html('span', { class: 'quote' }, defrag(unwrap(ns)))),
|
|
27
27
|
].reverse())),
|
|
28
28
|
false));
|
|
@@ -21,6 +21,6 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
|
|
|
21
21
|
visualize(fmap(some(inline), (ns, { source, position }) =>
|
|
22
22
|
source[position - 1] === '\n'
|
|
23
23
|
? ns
|
|
24
|
-
: ns.push(new Node(html('br'), Flag.
|
|
24
|
+
: ns.push(new Node(html('br'), Flag.blank)) && ns)))
|
|
25
25
|
])),
|
|
26
26
|
ns => new List([new Node(html('p', defrag(unwrap(trimBlankNodeEnd(ns)))))]))));
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { AnnotationParser } from '../inline';
|
|
2
2
|
import { State, Backtrack } from '../context';
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
|
-
import { union, some, precedence, state, constraint, surround, setBacktrack, lazy } from '../../combinator';
|
|
4
|
+
import { union, some, precedence, state, constraint, surround, close, setBacktrack, lazy } from '../../combinator';
|
|
5
5
|
import { inline } from '../inline';
|
|
6
6
|
import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
|
|
7
7
|
import { unwrap } from '../util';
|
|
8
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
9
|
|
|
10
10
|
export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, surround(
|
|
11
|
-
'((',
|
|
11
|
+
close('((', beforeNonblank),
|
|
12
12
|
precedence(1, state(State.annotation,
|
|
13
|
-
|
|
13
|
+
some(union([inline]), ')', [[')', 1]]))),
|
|
14
14
|
'))',
|
|
15
15
|
false,
|
|
16
16
|
[2, 1 | Backtrack.common, 3 | Backtrack.doublebracket],
|
|
@@ -8,7 +8,7 @@ import { unwrap, repeat } from '../util';
|
|
|
8
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
9
|
|
|
10
10
|
export const deletion: DeletionParser = lazy(() =>
|
|
11
|
-
precedence(0, recursion(Recursion.inline, repeat('~~', surround(
|
|
11
|
+
precedence(0, recursion(Recursion.inline, repeat('~~', '', surround(
|
|
12
12
|
'',
|
|
13
13
|
some(union([
|
|
14
14
|
some(inline, blankWith('\n', '~~')),
|
|
@@ -5,17 +5,17 @@ import { union, some, recursion, precedence, surround, lazy } from '../../combin
|
|
|
5
5
|
import { inline } from '../inline';
|
|
6
6
|
import { strong } from './strong';
|
|
7
7
|
import { str } from '../source';
|
|
8
|
-
import {
|
|
8
|
+
import { beforeNonblankWith, afterNonblank } from '../visibility';
|
|
9
9
|
import { unwrap } from '../util';
|
|
10
10
|
import { html, defrag } from 'typed-dom/dom';
|
|
11
11
|
|
|
12
12
|
export const emphasis: EmphasisParser = lazy(() => surround(
|
|
13
|
-
str('*', (
|
|
13
|
+
str('*', beforeNonblankWith(/(?!\*)/)),
|
|
14
14
|
precedence(0, recursion(Recursion.inline,
|
|
15
|
-
|
|
15
|
+
some(union([
|
|
16
16
|
some(inline, '*', afterNonblank),
|
|
17
17
|
strong,
|
|
18
|
-
]))))
|
|
18
|
+
])))),
|
|
19
19
|
str('*'),
|
|
20
20
|
false, [],
|
|
21
21
|
([, bs]) => new List([new Node(html('em', defrag(unwrap(bs))))]),
|
|
@@ -114,6 +114,7 @@ describe('Unit: parser/inline/emstrong', () => {
|
|
|
114
114
|
assert.deepStrictEqual(inspect(parser, input('******a*b ***', new Context())), [['*****', '<em>a</em>', 'b ', '***'], '']);
|
|
115
115
|
assert.deepStrictEqual(inspect(parser, input('******a*b ****', new Context())), [['*****', '<em>a</em>', 'b ', '****'], '']);
|
|
116
116
|
assert.deepStrictEqual(inspect(parser, input('******a*b *****', new Context())), [['*****', '<em>a</em>', 'b ', '*****'], '']);
|
|
117
|
+
assert.deepStrictEqual(inspect(parser, input('******a*** b***', new Context())), [['<em><strong><em><strong>a</strong></em> b</strong></em>'], '']);
|
|
117
118
|
});
|
|
118
119
|
|
|
119
120
|
});
|
|
@@ -23,9 +23,9 @@ const subemphasis: Parser.IntermediateParser<EmphasisParser> = lazy(() => some(u
|
|
|
23
23
|
// 可能な限り早く閉じるよう解析しなければならない。
|
|
24
24
|
// このため終端記号の後ろを見て終端を中止し同じ構文を再帰的に適用してはならない。
|
|
25
25
|
export const emstrong: EmStrongParser = lazy(() =>
|
|
26
|
-
precedence(0, recursion(Recursion.inline, repeat('***', surround(
|
|
26
|
+
precedence(0, recursion(Recursion.inline, repeat('***', beforeNonblank, surround(
|
|
27
27
|
'',
|
|
28
|
-
|
|
28
|
+
some(union([some(inline, '*', afterNonblank)])),
|
|
29
29
|
strs('*', 3),
|
|
30
30
|
false, [],
|
|
31
31
|
([, bs, cs], context): Result<Parser.Node<EmStrongParser>, Parser.Context<EmStrongParser>> => {
|
|
@@ -33,7 +33,7 @@ export const emstrong: EmStrongParser = lazy(() =>
|
|
|
33
33
|
const { buffer } = context;
|
|
34
34
|
switch (cs.head!.value) {
|
|
35
35
|
case '***':
|
|
36
|
-
return bs;
|
|
36
|
+
return buffer.import(bs);
|
|
37
37
|
case '**':
|
|
38
38
|
return bind<EmphasisParser>(
|
|
39
39
|
subemphasis,
|
|
@@ -13,13 +13,12 @@ import { html, define, defrag } from 'typed-dom/dom';
|
|
|
13
13
|
import IndexParser = ExtensionParser.IndexParser;
|
|
14
14
|
|
|
15
15
|
export const index: IndexParser = lazy(() => constraint(State.index, fmap(indexee(surround(
|
|
16
|
-
str('[#'),
|
|
16
|
+
str('[#', beforeNonblank),
|
|
17
17
|
precedence(1, state(State.linkers,
|
|
18
|
-
beforeNonblank(
|
|
19
18
|
some(inits([
|
|
20
19
|
inline,
|
|
21
20
|
signature,
|
|
22
|
-
]), ']', [[']', 1]])))
|
|
21
|
+
]), ']', [[']', 1]]))),
|
|
23
22
|
str(']'),
|
|
24
23
|
false,
|
|
25
24
|
[3 | Backtrack.common],
|
|
@@ -14,9 +14,9 @@ import { html } from 'typed-dom/dom';
|
|
|
14
14
|
|
|
15
15
|
export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => surround(
|
|
16
16
|
// ^はabbrで使用済みだが^:などのようにして分離使用可能
|
|
17
|
-
str(/\[[:^|]/y),
|
|
17
|
+
str(/\[[:^|]/y, beforeNonblank),
|
|
18
18
|
precedence(1, recursion(Recursion.inline,
|
|
19
|
-
|
|
19
|
+
some(union([inline]), ']', [[']', 1]]))),
|
|
20
20
|
str(']'),
|
|
21
21
|
false,
|
|
22
22
|
[3 | Backtrack.common],
|
|
@@ -5,7 +5,7 @@ import { Flag } from '../node';
|
|
|
5
5
|
import { union, some, recursion, precedence, validate, surround, open, match, lazy } from '../../combinator';
|
|
6
6
|
import { inline } from '../inline';
|
|
7
7
|
import { str } from '../source';
|
|
8
|
-
import {
|
|
8
|
+
import { isNonblankFirstLine, blankWith } from '../visibility';
|
|
9
9
|
import { invalid, unwrap } from '../util';
|
|
10
10
|
import { memoize } from 'spica/memoize';
|
|
11
11
|
import { html as h, defrag } from 'typed-dom/dom';
|
|
@@ -29,7 +29,7 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[ >])/yi,
|
|
|
29
29
|
open(str(/ ?/y), str('>'), true),
|
|
30
30
|
true, [],
|
|
31
31
|
([as, bs = new List(), cs], context) =>
|
|
32
|
-
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context), as.head!.value === '<wbr' ? Flag.
|
|
32
|
+
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context), as.head!.value === '<wbr' ? Flag.blank : Flag.none)]),
|
|
33
33
|
([as, bs = new List()], context) =>
|
|
34
34
|
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
|
|
35
35
|
match(
|
|
@@ -85,7 +85,7 @@ function elem(tag: string, content: boolean, as: readonly string[], bs: List<Nod
|
|
|
85
85
|
if (content) {
|
|
86
86
|
if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, context);
|
|
87
87
|
if (bs.length === 0) return ielem('content', `Missing the content`, context);
|
|
88
|
-
if (!
|
|
88
|
+
if (!isNonblankFirstLine(bs)) return ielem('content', `Missing the visible content in the same line`, context);
|
|
89
89
|
}
|
|
90
90
|
const [attrs] = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -1 : as.length));
|
|
91
91
|
if (/(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')) return ielem('attribute', 'Invalid HTML attribute', context)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HTMLEntityParser, UnsafeHTMLEntityParser } from '../inline';
|
|
2
2
|
import { Backtrack } from '../context';
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
|
-
import { Flag,
|
|
4
|
+
import { Flag, isBlankHTMLEntityName } from '../node';
|
|
5
5
|
import { union, surround, fmap } from '../../combinator';
|
|
6
6
|
import { str } from '../source';
|
|
7
7
|
import { invalid } from '../util';
|
|
@@ -15,7 +15,7 @@ export const unsafehtmlentity: UnsafeHTMLEntityParser = surround(
|
|
|
15
15
|
new List([
|
|
16
16
|
new Node(
|
|
17
17
|
parser(as.head!.value + bs.head!.value + cs.head!.value),
|
|
18
|
-
|
|
18
|
+
isBlankHTMLEntityName(bs.head!.value) ? Flag.blank : Flag.none)
|
|
19
19
|
]),
|
|
20
20
|
([as, bs]) =>
|
|
21
21
|
new List([new Node(as.head!.value + (bs?.head?.value ?? ''))]));
|
|
@@ -8,7 +8,7 @@ import { unwrap, repeat } from '../util';
|
|
|
8
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
9
|
|
|
10
10
|
export const insertion: InsertionParser = lazy(() =>
|
|
11
|
-
precedence(0, recursion(Recursion.inline, repeat('++', surround(
|
|
11
|
+
precedence(0, recursion(Recursion.inline, repeat('++', '', surround(
|
|
12
12
|
'',
|
|
13
13
|
some(union([
|
|
14
14
|
some(inline, blankWith('\n', '++')),
|
|
@@ -57,6 +57,7 @@ describe('Unit: parser/inline/italic', () => {
|
|
|
57
57
|
assert.deepStrictEqual(inspect(parser, input('//////a/////b', new Context())), [['///', '<i>a</i>', '//b'], '']);
|
|
58
58
|
assert.deepStrictEqual(inspect(parser, input('//////a//////', new Context())), [['<i><i>a</i></i>'], '']);
|
|
59
59
|
assert.deepStrictEqual(inspect(parser, input('//////a///b///', new Context())), [['<i><i>a</i>b</i>'], '']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser, input('//////a/// b///', new Context())), [['<i><i>a</i> b</i>'], '']);
|
|
60
61
|
assert.deepStrictEqual(inspect(parser, input('///a ///b//////', new Context())), [['<i>a <i>b</i></i>'], '']);
|
|
61
62
|
assert.deepStrictEqual(inspect(parser, input('///- ///b//////', new Context())), [['<i>- <i>b</i></i>'], '']);
|
|
62
63
|
assert.deepStrictEqual(inspect(parser, input('///a\\ ///b//////', new Context())), [['<i>a <i>b</i></i>'], '']);
|
|
@@ -11,9 +11,9 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
11
11
|
// 斜体は単語に使うとかえって見づらく読み飛ばしやすくなるため使わないべきであり
|
|
12
12
|
// ある程度の長さのある文に使うのが望ましい。
|
|
13
13
|
export const italic: ItalicParser = lazy(() =>
|
|
14
|
-
precedence(0, recursion(Recursion.inline, repeat('///', surround(
|
|
14
|
+
precedence(0, recursion(Recursion.inline, repeat('///', beforeNonblank, surround(
|
|
15
15
|
'',
|
|
16
|
-
|
|
16
|
+
some(union([inline]), '///', afterNonblank),
|
|
17
17
|
'///',
|
|
18
18
|
false, [],
|
|
19
19
|
([, bs], { buffer }) => buffer.import(bs),
|