securemark 0.295.8 → 0.296.0
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 +1 -1
- package/dist/index.js +268 -282
- package/package.json +1 -1
- package/src/combinator/control/constraint/contract.ts +5 -21
- package/src/combinator/data/parser/context.ts +3 -1
- package/src/combinator/data/parser/sequence.ts +3 -3
- package/src/combinator/data/parser.ts +7 -2
- package/src/parser/api/normalize.ts +10 -7
- package/src/parser/block/extension/figbase.test.ts +3 -0
- package/src/parser/block/extension/figbase.ts +1 -1
- package/src/parser/block/paragraph.test.ts +2 -2
- package/src/parser/block/reply/cite.ts +2 -1
- package/src/parser/block/reply/quote.ts +2 -1
- package/src/parser/block/reply.ts +2 -1
- package/src/parser/inline/annotation.test.ts +4 -5
- package/src/parser/inline/annotation.ts +2 -2
- package/src/parser/inline/emphasis.ts +3 -3
- package/src/parser/inline/emstrong.ts +4 -4
- package/src/parser/inline/extension/index.ts +2 -2
- package/src/parser/inline/extension/indexer.ts +3 -3
- package/src/parser/inline/extension/placeholder.ts +2 -2
- package/src/parser/inline/html.ts +2 -1
- package/src/parser/inline/htmlentity.test.ts +1 -1
- package/src/parser/inline/htmlentity.ts +10 -5
- package/src/parser/inline/italic.ts +2 -2
- package/src/parser/inline/link.test.ts +5 -4
- package/src/parser/inline/link.ts +6 -4
- package/src/parser/inline/mark.ts +2 -2
- package/src/parser/inline/media.test.ts +5 -2
- package/src/parser/inline/media.ts +9 -4
- package/src/parser/inline/reference.test.ts +5 -6
- package/src/parser/inline/reference.ts +2 -2
- package/src/parser/inline/remark.ts +1 -1
- package/src/parser/inline/strong.ts +3 -3
- package/src/parser/node.ts +17 -0
- package/src/parser/source/str.ts +9 -7
- package/src/parser/source/text.ts +4 -4
- package/src/parser/visibility.ts +53 -75
- /package/src/combinator/data/{data.ts → node.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Parser, Input, List, Node, Context
|
|
2
|
-
import { matcher } from '../../../combinator';
|
|
1
|
+
import { Parser, Input, List, Node, Context } from '../../data/parser';
|
|
2
|
+
import { matcher, bind } from '../../../combinator';
|
|
3
3
|
|
|
4
4
|
//export function contract<P extends Parser>(patterns: string | RegExp | (string | RegExp)[], parser: P, cond: (nodes: readonly Data<P>[], rest: string) => boolean): P;
|
|
5
5
|
//export function contract<N>(patterns: string | RegExp | (string | RegExp)[], parser: Parser<N>, cond: (nodes: readonly N[], rest: string) => boolean): Parser<N> {
|
|
@@ -11,32 +11,16 @@ export function validate<P extends Parser>(cond: ((input: Input<Parser.Context<P
|
|
|
11
11
|
export function validate<N>(pattern: string | RegExp | ((input: Input<Context>) => boolean), parser: Parser<N>): Parser<N> {
|
|
12
12
|
if (typeof pattern === 'function') return guard(pattern, parser);
|
|
13
13
|
const match = matcher(pattern, false);
|
|
14
|
-
return input =>
|
|
15
|
-
const { context } = input;
|
|
16
|
-
const { source, position } = context;
|
|
17
|
-
if (position === source.length) return;
|
|
18
|
-
if (!match(input)) return;
|
|
19
|
-
return parser(input);
|
|
20
|
-
};
|
|
14
|
+
return input => match(input) ? parser(input) : undefined;
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
function guard<P extends Parser>(f: (input: Input<Parser.Context<P>>) => boolean, parser: P): P;
|
|
24
18
|
function guard<N>(f: (input: Input<Context>) => boolean, parser: Parser<N>): Parser<N> {
|
|
25
|
-
return input =>
|
|
26
|
-
f(input)
|
|
27
|
-
? parser(input)
|
|
28
|
-
: undefined;
|
|
19
|
+
return input => f(input) ? parser(input) : undefined;
|
|
29
20
|
}
|
|
30
21
|
|
|
31
22
|
export function verify<P extends Parser>(parser: P, cond: (nodes: List<Node<Parser.Node<P>>>, context: Parser.Context<P>) => boolean): P;
|
|
32
23
|
export function verify<N>(parser: Parser<N>, cond: (nodes: List<Node<N>>, context: Context) => boolean): Parser<N> {
|
|
33
24
|
assert(parser);
|
|
34
|
-
return
|
|
35
|
-
const { context } = input;
|
|
36
|
-
const { source, position } = context;
|
|
37
|
-
if (position === source.length) return;
|
|
38
|
-
const result = parser(input);
|
|
39
|
-
if (result && !cond(result, context)) return;
|
|
40
|
-
return result;
|
|
41
|
-
});
|
|
25
|
+
return bind(parser, (nodes, context) => cond(nodes, context) ? nodes : undefined)
|
|
42
26
|
}
|
|
@@ -159,7 +159,7 @@ export function constraint<N>(state: number, positive: boolean | Parser<N>, pars
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
export function matcher(pattern: string | RegExp, advance: boolean): Parser<string> {
|
|
162
|
+
export function matcher(pattern: string | RegExp, advance: boolean, verify?: (source: string, position: number, range: number) => boolean): Parser<string> {
|
|
163
163
|
assert(pattern instanceof RegExp ? !pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^') : true);
|
|
164
164
|
const count = typeof pattern === 'object'
|
|
165
165
|
? /[^^\\*+][*+]/.test(pattern.source)
|
|
@@ -169,6 +169,7 @@ export function matcher(pattern: string | RegExp, advance: boolean): Parser<stri
|
|
|
169
169
|
return ({ context }) => {
|
|
170
170
|
const { source, position } = context;
|
|
171
171
|
if (!source.startsWith(pattern, position)) return;
|
|
172
|
+
if (verify?.(source, position, pattern.length) === false) return;
|
|
172
173
|
if (advance) {
|
|
173
174
|
context.position += pattern.length;
|
|
174
175
|
}
|
|
@@ -182,6 +183,7 @@ export function matcher(pattern: string | RegExp, advance: boolean): Parser<stri
|
|
|
182
183
|
if (!pattern.test(source)) return;
|
|
183
184
|
const src = source.slice(position, pattern.lastIndex);
|
|
184
185
|
count && consume(src.length, context);
|
|
186
|
+
if (verify?.(source, position, src.length) === false) return;
|
|
185
187
|
if (advance) {
|
|
186
188
|
context.position += src.length;
|
|
187
189
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Parser, List, Node, Context } from '../parser';
|
|
1
|
+
import { Parser, List, Node, Context, failsafe } from '../parser';
|
|
2
2
|
|
|
3
3
|
export function sequence<P extends Parser>(parsers: Parser.SubParsers<P>): Parser.SubNode<P> extends Parser.Node<P> ? P : Parser<Parser.SubNode<P>, Parser.Context<P>, Parser.SubParsers<P>>;
|
|
4
4
|
export function sequence<N, D extends Parser<N>[]>(parsers: D): Parser<N, Context, D> {
|
|
5
5
|
assert(parsers.every(f => f));
|
|
6
6
|
if (parsers.length === 1) return parsers[0];
|
|
7
|
-
return input => {
|
|
7
|
+
return failsafe(input => {
|
|
8
8
|
const { context } = input;
|
|
9
9
|
const { source } = context;
|
|
10
10
|
let nodes: List<Node<N>> | undefined;
|
|
@@ -16,5 +16,5 @@ export function sequence<N, D extends Parser<N>[]>(parsers: D): Parser<N, Contex
|
|
|
16
16
|
nodes = nodes?.import(result) ?? result;
|
|
17
17
|
}
|
|
18
18
|
return nodes;
|
|
19
|
-
};
|
|
19
|
+
});
|
|
20
20
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { List } from './
|
|
1
|
+
import { List } from './node';
|
|
2
2
|
import { Delimiters } from './delimiter';
|
|
3
3
|
|
|
4
4
|
export type Parser<N = unknown, C extends Context = Context, D extends Parser<unknown, C>[] = any>
|
|
@@ -20,7 +20,10 @@ export type Result<N, C extends Context = Context, D extends Parser<unknown, C>[
|
|
|
20
20
|
| undefined;
|
|
21
21
|
export { List };
|
|
22
22
|
export class Node<N> implements List.Node {
|
|
23
|
-
constructor(
|
|
23
|
+
constructor(
|
|
24
|
+
public value: N,
|
|
25
|
+
public flags: number = 0,
|
|
26
|
+
) {
|
|
24
27
|
}
|
|
25
28
|
public next?: this = undefined;
|
|
26
29
|
public prev?: this = undefined;
|
|
@@ -66,6 +69,8 @@ export class Context {
|
|
|
66
69
|
// Objectの内部実装を利用する。
|
|
67
70
|
// 探索木を直接使用する場合は探索速度が重要で挿入は相対的に少なく削除は不要かつ不確実であるため
|
|
68
71
|
// AVL木が適当と思われる。
|
|
72
|
+
// メモリの局所性を得るために木ごとに最初の数十から数百byte分のノードをプールしノードが不足した場合は
|
|
73
|
+
// 使い捨てノードを追加またはテーブルに移行するとよいだろう。
|
|
69
74
|
// 最大セグメントサイズ10KB内で探索コストが平均実行性能を圧迫するほど大きくなるとは考えにくいが
|
|
70
75
|
// 探索コストを減らすにはバックトラック位置数が規定数を超えた場合一定区間ごとに探索木を分割する方法が考えられる。
|
|
71
76
|
// 10KBの入力すべてを保持する探索木を1024文字ごとに分割するために必要なテーブルサイズは64bit*98=784byteとなる。
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Context } from '../context';
|
|
3
|
-
import { unsafehtmlentity } from '../inline/htmlentity';
|
|
1
|
+
import { html } from 'typed-dom/dom';
|
|
4
2
|
|
|
5
3
|
const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
|
|
6
4
|
assert(UNICODE_REPLACEMENT_CHARACTER.trim());
|
|
@@ -15,7 +13,7 @@ function format(source: string): string {
|
|
|
15
13
|
|
|
16
14
|
const invalid = new RegExp([
|
|
17
15
|
/(?![\t\r\n])[\x00-\x1F\x7F]/g.source,
|
|
18
|
-
/(
|
|
16
|
+
/(?![\u200C\u200D])[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]/g.source,
|
|
19
17
|
// 後読みが重い
|
|
20
18
|
///(?<![\u1820\u1821])\u180E/g.source,
|
|
21
19
|
///[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g.source,
|
|
@@ -59,7 +57,12 @@ export const invisibleHTMLEntityNames = [
|
|
|
59
57
|
'it',
|
|
60
58
|
'InvisibleComma',
|
|
61
59
|
'ic',
|
|
62
|
-
] as
|
|
60
|
+
] as readonly string[];
|
|
61
|
+
const parser = (el => (entity: string): string => {
|
|
62
|
+
if (entity === '
') return entity;
|
|
63
|
+
el.innerHTML = entity;
|
|
64
|
+
return el.textContent!;
|
|
65
|
+
})(html('span'));
|
|
63
66
|
const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
|
|
64
67
|
'Tab',
|
|
65
68
|
'NewLine',
|
|
@@ -69,7 +72,7 @@ const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name =>
|
|
|
69
72
|
'zwnj',
|
|
70
73
|
].includes(name));
|
|
71
74
|
const unreadableEscapeCharacters = unreadableEscapeHTMLEntityNames
|
|
72
|
-
.map(name =>
|
|
75
|
+
.map(name => parser(`&${name};`));
|
|
73
76
|
assert(unreadableEscapeCharacters.length === unreadableEscapeHTMLEntityNames.length);
|
|
74
77
|
assert(unreadableEscapeCharacters.every(c => c.length === 1));
|
|
75
78
|
const unreadableEscapeCharacter = new RegExp(`[${unreadableEscapeCharacters.join('')}]`, 'g');
|
|
@@ -84,7 +87,7 @@ const unreadableSpecialCharacters = [
|
|
|
84
87
|
// ZERO WIDTH SPACE
|
|
85
88
|
'\u200B',
|
|
86
89
|
// ZERO WIDTH NON-JOINER
|
|
87
|
-
'\u200C',
|
|
90
|
+
//'\u200C',
|
|
88
91
|
// ZERO WIDTH JOINER
|
|
89
92
|
//'\u200D',
|
|
90
93
|
// LEFT-TO-RIGHT MARK
|
|
@@ -13,10 +13,13 @@ describe('Unit: parser/block/extension/figbase', () => {
|
|
|
13
13
|
assert.deepStrictEqual(inspect(parser, input('$-0.', new Context())), undefined);
|
|
14
14
|
assert.deepStrictEqual(inspect(parser, input('$-0.1', new Context())), undefined);
|
|
15
15
|
assert.deepStrictEqual(inspect(parser, input('$-1', new Context())), undefined);
|
|
16
|
+
assert.deepStrictEqual(inspect(parser, input('$-0 0', new Context())), undefined);
|
|
16
17
|
assert.deepStrictEqual(inspect(parser, input('$-0\n 0', new Context())), undefined);
|
|
17
18
|
assert.deepStrictEqual(inspect(parser, input('$-name', new Context())), undefined);
|
|
18
19
|
assert.deepStrictEqual(inspect(parser, input('$-name-0', new Context())), undefined);
|
|
19
20
|
assert.deepStrictEqual(inspect(parser, input('$group-0', new Context())), undefined);
|
|
21
|
+
assert.deepStrictEqual(inspect(parser, input('$-0]', new Context())), undefined);
|
|
22
|
+
assert.deepStrictEqual(inspect(parser, input('[$-0', new Context())), undefined);
|
|
20
23
|
assert.deepStrictEqual(inspect(parser, input(' [$-0]', new Context())), undefined);
|
|
21
24
|
});
|
|
22
25
|
|
|
@@ -5,7 +5,7 @@ import { label } from '../../inline/extension/label';
|
|
|
5
5
|
import { html } from 'typed-dom/dom';
|
|
6
6
|
|
|
7
7
|
export const figbase: ExtensionParser.FigbaseParser = block(fmap(
|
|
8
|
-
validate(/\[?\$-(?:[0-9]+\.)*0\]?
|
|
8
|
+
validate(/\[?\$-(?:[0-9]+\.)*0\]?[^\S\n]*(?:$|\n)/y,
|
|
9
9
|
line(union([label]))),
|
|
10
10
|
([{ value: el }]) => {
|
|
11
11
|
const label = el.getAttribute('data-label')!;
|
|
@@ -22,8 +22,8 @@ describe('Unit: parser/block/paragraph', () => {
|
|
|
22
22
|
assert.deepStrictEqual(inspect(parser, input('a\\ \n', new Context())), [['<p>a</p>'], '']);
|
|
23
23
|
assert.deepStrictEqual(inspect(parser, input('a\\\n', new Context())), [['<p>a</p>'], '']);
|
|
24
24
|
assert.deepStrictEqual(inspect(parser, input('a\\\nb', new Context())), [['<p>a<br>b</p>'], '']);
|
|
25
|
-
assert.deepStrictEqual(inspect(parser, input('a
b', new Context())), [['<p>a b</p>'], '']);
|
|
26
|
-
assert.deepStrictEqual(inspect(parser, input('	&
|
|
25
|
+
assert.deepStrictEqual(inspect(parser, input('a
b', new Context())), [['<p>a<span class="invalid">&NewLine;</span>b</p>'], '']);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser, input('	 ', new Context())), [['<p>&Tab;</p>'], '']);
|
|
27
27
|
assert.deepStrictEqual(inspect(parser, input('<wbr>', new Context())), [['<p><wbr></p>'], '']);
|
|
28
28
|
assert.deepStrictEqual(inspect(parser, input('<wbr>\n', new Context())), [['<p><wbr></p>'], '']);
|
|
29
29
|
assert.deepStrictEqual(inspect(parser, input('<wbr>\na', new Context())), [['<p><wbr><br>a</p>'], '']);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReplyParser } from '../../block';
|
|
2
2
|
import { List, Node } from '../../../combinator/data/parser';
|
|
3
|
+
import { Flag } from '../../node';
|
|
3
4
|
import { union, line, focus, open, fmap } from '../../../combinator';
|
|
4
5
|
import { anchor } from '../../inline/autolink/anchor';
|
|
5
6
|
import { str } from '../../source';
|
|
@@ -33,6 +34,6 @@ export const cite: ReplyParser.CiteParser = line(fmap(
|
|
|
33
34
|
? define(node, { 'data-depth': `${quotes.length + 1}` }, node.innerText.slice(1))
|
|
34
35
|
: node.slice(1),
|
|
35
36
|
]))),
|
|
36
|
-
new Node(html('br')),
|
|
37
|
+
new Node(html('br'), Flag.invisible),
|
|
37
38
|
]);
|
|
38
39
|
}));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReplyParser } from '../../block';
|
|
2
2
|
import { List, Node } from '../../../combinator/data/parser';
|
|
3
|
+
import { Flag } from '../../node';
|
|
3
4
|
import { union, some, block, validate, rewrite, convert, lazy, fmap } from '../../../combinator';
|
|
4
5
|
import { math } from '../../inline/math';
|
|
5
6
|
import { autolink } from '../../inline/autolink';
|
|
@@ -21,7 +22,7 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
|
|
|
21
22
|
unescsource,
|
|
22
23
|
])))),
|
|
23
24
|
(ns, { source, position }) => new List([
|
|
24
|
-
new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br')),
|
|
25
|
+
new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.invisible),
|
|
25
26
|
new Node(html('span', { class: 'quote' }, defrag(unwrap(ns)))),
|
|
26
27
|
].reverse())),
|
|
27
28
|
false));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReplyParser } from '../block';
|
|
2
2
|
import { List, Node } from '../../combinator/data/parser';
|
|
3
|
+
import { Flag } from '../node';
|
|
3
4
|
import { union, some, block, validate, rewrite, fmap } from '../../combinator';
|
|
4
5
|
import { cite, syntax as csyntax } from './reply/cite';
|
|
5
6
|
import { quote, syntax as qsyntax } from './reply/quote';
|
|
@@ -20,6 +21,6 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
|
|
|
20
21
|
visualize(fmap(some(inline), (ns, { source, position }) =>
|
|
21
22
|
source[position - 1] === '\n'
|
|
22
23
|
? ns
|
|
23
|
-
: ns.push(new Node(html('br'))) && ns)))
|
|
24
|
+
: ns.push(new Node(html('br'), Flag.invisible)) && ns)))
|
|
24
25
|
])),
|
|
25
26
|
ns => new List([new Node(html('p', defrag(unwrap(trimBlankNodeEnd(ns)))))]))));
|
|
@@ -19,7 +19,10 @@ describe('Unit: parser/inline/annotation', () => {
|
|
|
19
19
|
assert.deepStrictEqual(inspect(parser, input('(([))', new Context())), undefined);
|
|
20
20
|
assert.deepStrictEqual(inspect(parser, input('(([%))', new Context())), undefined);
|
|
21
21
|
assert.deepStrictEqual(inspect(parser, input('(( ))', new Context())), undefined);
|
|
22
|
-
assert.deepStrictEqual(inspect(parser, input('((
|
|
22
|
+
assert.deepStrictEqual(inspect(parser, input('(( a))', new Context())), undefined);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser, input('(( a ))', new Context())), undefined);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser, input('((\\ a))', new Context())), undefined);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser, input('((<wbr>a))', new Context())), undefined);
|
|
23
26
|
assert.deepStrictEqual(inspect(parser, input('((\n))', new Context())), undefined);
|
|
24
27
|
assert.deepStrictEqual(inspect(parser, input('((\na))', new Context())), undefined);
|
|
25
28
|
assert.deepStrictEqual(inspect(parser, input('((\\\na))', new Context())), undefined);
|
|
@@ -35,10 +38,6 @@ describe('Unit: parser/inline/annotation', () => {
|
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
it('basic', () => {
|
|
38
|
-
assert.deepStrictEqual(inspect(parser, input('(( a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
39
|
-
assert.deepStrictEqual(inspect(parser, input('(( a ))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
40
|
-
assert.deepStrictEqual(inspect(parser, input('((\\ a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
41
|
-
assert.deepStrictEqual(inspect(parser, input('((<wbr>a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
42
41
|
assert.deepStrictEqual(inspect(parser, input('((a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
43
42
|
assert.deepStrictEqual(inspect(parser, input('((a ))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
44
43
|
assert.deepStrictEqual(inspect(parser, input('((a ))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
|
|
@@ -3,14 +3,14 @@ import { State, Backtrack } from '../context';
|
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
4
|
import { union, some, precedence, state, constraint, surround, setBacktrack, lazy } from '../../combinator';
|
|
5
5
|
import { inline } from '../inline';
|
|
6
|
-
import {
|
|
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
|
'((',
|
|
12
12
|
precedence(1, state(State.annotation,
|
|
13
|
-
|
|
13
|
+
beforeNonblank(some(union([inline]), ')', [[')', 1]])))),
|
|
14
14
|
'))',
|
|
15
15
|
false,
|
|
16
16
|
[2, 1 | Backtrack.common, 3 | Backtrack.doublebracket],
|
|
@@ -5,14 +5,14 @@ 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 { beforeNonblank, 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('*', (source, position, range) => !source.startsWith('*', position + range)),
|
|
14
14
|
precedence(0, recursion(Recursion.inline,
|
|
15
|
-
|
|
15
|
+
beforeNonblank(some(union([
|
|
16
16
|
some(inline, '*', afterNonblank),
|
|
17
17
|
strong,
|
|
18
18
|
]))))),
|
|
@@ -5,8 +5,8 @@ import { union, some, recursion, precedence, surround, lazy, bind } from '../../
|
|
|
5
5
|
import { inline } from '../inline';
|
|
6
6
|
import { strong } from './strong';
|
|
7
7
|
import { emphasis } from './emphasis';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { strs } from '../source';
|
|
9
|
+
import { beforeNonblank, afterNonblank } from '../visibility';
|
|
10
10
|
import { unwrap, repeat } from '../util';
|
|
11
11
|
import { html, defrag } from 'typed-dom/dom';
|
|
12
12
|
|
|
@@ -25,8 +25,8 @@ const subemphasis: Parser.IntermediateParser<EmphasisParser> = lazy(() => some(u
|
|
|
25
25
|
export const emstrong: EmStrongParser = lazy(() =>
|
|
26
26
|
precedence(0, recursion(Recursion.inline, repeat('***', surround(
|
|
27
27
|
'',
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
beforeNonblank(some(union([some(inline, '*', afterNonblank)]))),
|
|
29
|
+
strs('*', 3),
|
|
30
30
|
false, [],
|
|
31
31
|
([, bs, cs], context): Result<Parser.Node<EmStrongParser>, Parser.Context<EmStrongParser>> => {
|
|
32
32
|
assert(cs.length === 1);
|
|
@@ -6,7 +6,7 @@ import { inline } from '../../inline';
|
|
|
6
6
|
import { indexee, identity } from './indexee';
|
|
7
7
|
import { unsafehtmlentity } from '../htmlentity';
|
|
8
8
|
import { txt, str } from '../../source';
|
|
9
|
-
import {
|
|
9
|
+
import { beforeNonblank, trimBlankNodeEnd } from '../../visibility';
|
|
10
10
|
import { unwrap } from '../../util';
|
|
11
11
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ import IndexParser = ExtensionParser.IndexParser;
|
|
|
15
15
|
export const index: IndexParser = lazy(() => constraint(State.index, fmap(indexee(surround(
|
|
16
16
|
str('[#'),
|
|
17
17
|
precedence(1, state(State.linkers,
|
|
18
|
-
|
|
18
|
+
beforeNonblank(
|
|
19
19
|
some(inits([
|
|
20
20
|
inline,
|
|
21
21
|
signature,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ExtensionParser } from '../../inline';
|
|
2
2
|
import { List, Node } from '../../../combinator/data/parser';
|
|
3
|
-
import { union, focus, surround } from '../../../combinator';
|
|
3
|
+
import { union, validate, focus, surround } from '../../../combinator';
|
|
4
4
|
import { signature } from './index';
|
|
5
5
|
import { html } from 'typed-dom/dom';
|
|
6
6
|
|
|
@@ -10,10 +10,10 @@ import { html } from 'typed-dom/dom';
|
|
|
10
10
|
// テキストまたはインデクスを付けて同期が必要な機会を減らすのが
|
|
11
11
|
// 継続的編集において最も簡便となる。
|
|
12
12
|
|
|
13
|
-
export const indexer: ExtensionParser.IndexerParser = surround(
|
|
13
|
+
export const indexer: ExtensionParser.IndexerParser = validate(' [|', surround(
|
|
14
14
|
/ \[(?=\|\S)/y,
|
|
15
15
|
union([
|
|
16
16
|
signature,
|
|
17
17
|
focus(/\|(?=\])/y, () => new List([new Node(html('span', { class: 'indexer', 'data-index': '' }))])),
|
|
18
18
|
]),
|
|
19
|
-
/\][^\S\n]*(?:$|\n)/y);
|
|
19
|
+
/\][^\S\n]*(?:$|\n)/y));
|
|
@@ -4,7 +4,7 @@ import { List, Node } from '../../../combinator/data/parser';
|
|
|
4
4
|
import { union, some, recursion, precedence, surround, lazy } from '../../../combinator';
|
|
5
5
|
import { inline } from '../../inline';
|
|
6
6
|
import { str } from '../../source';
|
|
7
|
-
import {
|
|
7
|
+
import { beforeNonblank } from '../../visibility';
|
|
8
8
|
import { invalid } from '../../util';
|
|
9
9
|
import { html } from 'typed-dom/dom';
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => surroun
|
|
|
16
16
|
// ^はabbrで使用済みだが^:などのようにして分離使用可能
|
|
17
17
|
str(/\[[:^|]/y),
|
|
18
18
|
precedence(1, recursion(Recursion.inline,
|
|
19
|
-
|
|
19
|
+
beforeNonblank(some(union([inline]), ']', [[']', 1]])))),
|
|
20
20
|
str(']'),
|
|
21
21
|
false,
|
|
22
22
|
[3 | Backtrack.common],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HTMLParser } from '../inline';
|
|
2
2
|
import { Recursion } from '../context';
|
|
3
3
|
import { List, Node, Context } from '../../combinator/data/parser';
|
|
4
|
+
import { Flag } from '../node';
|
|
4
5
|
import { union, some, recursion, precedence, validate, surround, open, match, lazy } from '../../combinator';
|
|
5
6
|
import { inline } from '../inline';
|
|
6
7
|
import { str } from '../source';
|
|
@@ -28,7 +29,7 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[ >])/yi,
|
|
|
28
29
|
open(str(/ ?/y), str('>'), true),
|
|
29
30
|
true, [],
|
|
30
31
|
([as, bs = new List(), cs], context) =>
|
|
31
|
-
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), 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.invisible : Flag.none)]),
|
|
32
33
|
([as, bs = new List()], context) =>
|
|
33
34
|
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
|
|
34
35
|
match(
|
|
@@ -33,11 +33,11 @@ describe('Unit: parser/inline/htmlentity', () => {
|
|
|
33
33
|
assert.deepStrictEqual(inspect(parser, input('"', new Context())), [['&'], '#X22;']);
|
|
34
34
|
assert.deepStrictEqual(inspect(parser, input('ആ', new Context())), [['&'], '#XD06;']);
|
|
35
35
|
assert.deepStrictEqual(inspect(parser, input('ಫ', new Context())), [['&'], '#xcab;']);
|
|
36
|
+
assert.deepStrictEqual(inspect(parser, input('
', new Context())), [['<span class="invalid">&NewLine;</span>'], '']);
|
|
36
37
|
assert.deepStrictEqual(inspect(parser, input(' &', new Context())), undefined);
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
it('entity', () => {
|
|
40
|
-
assert.deepStrictEqual(inspect(parser, input('
', new Context())), [[' '], '']);
|
|
41
41
|
assert.deepStrictEqual(inspect(parser, input(' ', new Context())), [['\u00A0'], '']);
|
|
42
42
|
assert.deepStrictEqual(inspect(parser, input('&', new Context())), [['&'], '']);
|
|
43
43
|
assert.deepStrictEqual(inspect(parser, input('©', new Context())), [['©'], '']);
|
|
@@ -1,6 +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, isInvisibleHTMLEntityName } from '../node';
|
|
4
5
|
import { union, surround, fmap } from '../../combinator';
|
|
5
6
|
import { str } from '../source';
|
|
6
7
|
import { invalid } from '../util';
|
|
@@ -11,15 +12,19 @@ export const unsafehtmlentity: UnsafeHTMLEntityParser = surround(
|
|
|
11
12
|
false,
|
|
12
13
|
[3 | Backtrack.unescapable],
|
|
13
14
|
([as, bs, cs]) =>
|
|
14
|
-
new List([
|
|
15
|
+
new List([
|
|
16
|
+
new Node(
|
|
17
|
+
parser(as.head!.value + bs.head!.value + cs.head!.value),
|
|
18
|
+
isInvisibleHTMLEntityName(bs.head!.value) ? Flag.invisible : Flag.none)
|
|
19
|
+
]),
|
|
15
20
|
([as, bs]) =>
|
|
16
21
|
new List([new Node(as.head!.value + (bs?.head?.value ?? ''))]));
|
|
17
22
|
|
|
18
23
|
export const htmlentity: HTMLEntityParser = fmap(
|
|
19
24
|
union([unsafehtmlentity]),
|
|
20
|
-
([{ value }]) => new List([
|
|
21
|
-
length === 1 || value.at(-1) !== ';'
|
|
22
|
-
? new Node(value)
|
|
25
|
+
([{ value, flags }]) => new List([
|
|
26
|
+
value.length === 1 || value.at(-1) !== ';'
|
|
27
|
+
? new Node(value, flags)
|
|
23
28
|
: new Node(html('span', {
|
|
24
29
|
class: 'invalid',
|
|
25
30
|
...invalid('htmlentity', 'syntax', 'Invalid HTML entity'),
|
|
@@ -27,7 +32,7 @@ export const htmlentity: HTMLEntityParser = fmap(
|
|
|
27
32
|
]));
|
|
28
33
|
|
|
29
34
|
const parser = (el => (entity: string): string => {
|
|
30
|
-
if (entity === '
') return
|
|
35
|
+
if (entity === '
') return entity;
|
|
31
36
|
el.innerHTML = entity;
|
|
32
37
|
return el.textContent!;
|
|
33
38
|
})(html('span'));
|
|
@@ -3,7 +3,7 @@ import { Recursion, Command } from '../context';
|
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
4
|
import { union, some, recursion, precedence, surround, lazy } from '../../combinator';
|
|
5
5
|
import { inline } from '../inline';
|
|
6
|
-
import {
|
|
6
|
+
import { beforeNonblank, afterNonblank } from '../visibility';
|
|
7
7
|
import { unwrap, repeat } from '../util';
|
|
8
8
|
import { html, defrag } from 'typed-dom/dom';
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ import { html, defrag } from 'typed-dom/dom';
|
|
|
13
13
|
export const italic: ItalicParser = lazy(() =>
|
|
14
14
|
precedence(0, recursion(Recursion.inline, repeat('///', surround(
|
|
15
15
|
'',
|
|
16
|
-
|
|
16
|
+
beforeNonblank(some(union([inline]), '///', afterNonblank)),
|
|
17
17
|
'///',
|
|
18
18
|
false, [],
|
|
19
19
|
([, bs], { buffer }) => buffer.import(bs),
|
|
@@ -76,10 +76,15 @@ describe('Unit: parser/inline/link', () => {
|
|
|
76
76
|
assert.deepStrictEqual(inspect(parser, input('[ ]{ }', new Context())), undefined);
|
|
77
77
|
assert.deepStrictEqual(inspect(parser, input('[ ]{b}', new Context())), undefined);
|
|
78
78
|
assert.deepStrictEqual(inspect(parser, input('[ ]{b}', new Context())), undefined);
|
|
79
|
+
assert.deepStrictEqual(inspect(parser, input('[ a]{b}', new Context())), undefined);
|
|
80
|
+
assert.deepStrictEqual(inspect(parser, input('[ a ]{b}', new Context())), undefined);
|
|
81
|
+
assert.deepStrictEqual(inspect(parser, input('[\\ a]{b}', new Context())), undefined);
|
|
82
|
+
assert.deepStrictEqual(inspect(parser, input('[ \\ a]{b}', new Context())), undefined);
|
|
79
83
|
assert.deepStrictEqual(inspect(parser, input('[\n]{b}', new Context())), undefined);
|
|
80
84
|
assert.deepStrictEqual(inspect(parser, input('[\\ ]{b}', new Context())), undefined);
|
|
81
85
|
assert.deepStrictEqual(inspect(parser, input('[\\\n]{b}', new Context())), undefined);
|
|
82
86
|
assert.deepStrictEqual(inspect(parser, input('[	]{b}', new Context())), undefined);
|
|
87
|
+
assert.deepStrictEqual(inspect(parser, input('[‍]{b}', new Context())), undefined);
|
|
83
88
|
assert.deepStrictEqual(inspect(parser, input('[[]{b}', new Context())), undefined);
|
|
84
89
|
assert.deepStrictEqual(inspect(parser, input('[]]{b}', new Context())), undefined);
|
|
85
90
|
assert.deepStrictEqual(inspect(parser, input('[a]{}', new Context())), undefined);
|
|
@@ -131,10 +136,6 @@ describe('Unit: parser/inline/link', () => {
|
|
|
131
136
|
assert.deepStrictEqual(inspect(parser, input('[]{^/b}', new Context({ host: new URL('/0.a0', location.origin) }))), [[`<a class="url" href="/b">^/b</a>`], '']);
|
|
132
137
|
assert.deepStrictEqual(inspect(parser, input('[]{^/b}', new Context({ host: new URL('/0.0', location.origin) }))), [[`<a class="url" href="/0.0/b">^/b</a>`], '']);
|
|
133
138
|
assert.deepStrictEqual(inspect(parser, input('[]{^/b}', new Context({ host: new URL('/0.0,0.0,0z', location.origin) }))), [[`<a class="url" href="/0.0,0.0,0z/b">^/b</a>`], '']);
|
|
134
|
-
assert.deepStrictEqual(inspect(parser, input('[ a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
135
|
-
assert.deepStrictEqual(inspect(parser, input('[ a ]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
136
|
-
assert.deepStrictEqual(inspect(parser, input('[\\ a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
137
|
-
assert.deepStrictEqual(inspect(parser, input('[ \\ a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
138
139
|
assert.deepStrictEqual(inspect(parser, input('[a ]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
139
140
|
assert.deepStrictEqual(inspect(parser, input('[a ]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
140
141
|
assert.deepStrictEqual(inspect(parser, input('[a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
|
|
@@ -5,7 +5,7 @@ import { union, inits, sequence, subsequence, some, consume, precedence, state,
|
|
|
5
5
|
import { inline, media, shortmedia } from '../inline';
|
|
6
6
|
import { attributes } from './html';
|
|
7
7
|
import { str } from '../source';
|
|
8
|
-
import {
|
|
8
|
+
import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
|
|
9
9
|
import { unwrap, invalid, stringify } from '../util';
|
|
10
10
|
import { ReadonlyURL } from 'spica/url';
|
|
11
11
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
@@ -20,7 +20,7 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => bind(
|
|
|
20
20
|
constraint(State.link, state(State.linkers, dup(surround(
|
|
21
21
|
'[',
|
|
22
22
|
precedence(1,
|
|
23
|
-
|
|
23
|
+
beforeNonblank(some(union([inline]), ']', [[']', 1]]))),
|
|
24
24
|
']',
|
|
25
25
|
true,
|
|
26
26
|
[3 | Backtrack.common | Backtrack.link, 2 | Backtrack.ruby],
|
|
@@ -43,7 +43,9 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => bind(
|
|
|
43
43
|
bs && as.import(bs).push(new Node(Command.Cancel)) && as)),
|
|
44
44
|
]),
|
|
45
45
|
([{ value: content }, { value: params = undefined } = {}], context) => {
|
|
46
|
-
if (context.state & State.link) return new List([
|
|
46
|
+
if (context.state & State.link) return new List([
|
|
47
|
+
new Node(context.source.slice(context.position - context.range, context.position).replace(/\\($|.)/g, '$1'))
|
|
48
|
+
]);
|
|
47
49
|
if (content.last!.value === Command.Separator) {
|
|
48
50
|
content.pop();
|
|
49
51
|
if (params === undefined) {
|
|
@@ -88,7 +90,7 @@ export const medialink: LinkParser.MediaLinkParser = lazy(() => constraint(State
|
|
|
88
90
|
new List([new Node(parse(content, params as List<Node<string>>, context))])))));
|
|
89
91
|
|
|
90
92
|
export const uri: LinkParser.ParameterParser.UriParser = union([
|
|
91
|
-
open(
|
|
93
|
+
open(' ', str(/\S+/y)),
|
|
92
94
|
str(/[^\s{}]+/y),
|
|
93
95
|
]);
|
|
94
96
|
|
|
@@ -4,14 +4,14 @@ import { List, Node } from '../../combinator/data/parser';
|
|
|
4
4
|
import { union, some, recursion, precedence, state, surround, lazy } from '../../combinator';
|
|
5
5
|
import { inline } from '../inline';
|
|
6
6
|
import { identity, signature } from './extension/indexee';
|
|
7
|
-
import {
|
|
7
|
+
import { beforeNonblank, afterNonblank } from '../visibility';
|
|
8
8
|
import { unwrap, repeat } from '../util';
|
|
9
9
|
import { html, define, defrag } from 'typed-dom/dom';
|
|
10
10
|
|
|
11
11
|
export const mark: MarkParser = lazy(() =>
|
|
12
12
|
precedence(0, recursion(Recursion.inline, repeat('==', surround(
|
|
13
13
|
'',
|
|
14
|
-
|
|
14
|
+
beforeNonblank(state(State.mark, some(union([inline]), '==', afterNonblank))),
|
|
15
15
|
'==',
|
|
16
16
|
false, [],
|
|
17
17
|
([, bs], { buffer }) => buffer.import(bs),
|
|
@@ -41,10 +41,15 @@ describe('Unit: parser/inline/media', () => {
|
|
|
41
41
|
assert.deepStrictEqual(inspect(parser, input('![ ]{}', new Context())), undefined);
|
|
42
42
|
assert.deepStrictEqual(inspect(parser, input('![ ]{b}', new Context())), undefined);
|
|
43
43
|
assert.deepStrictEqual(inspect(parser, input('![ ]{b}', new Context())), undefined);
|
|
44
|
+
assert.deepStrictEqual(inspect(parser, input('![ a]{b}', new Context())), undefined);
|
|
45
|
+
assert.deepStrictEqual(inspect(parser, input('![ a ]{b}', new Context())), undefined);
|
|
46
|
+
assert.deepStrictEqual(inspect(parser, input('![\\ a]{b}', new Context())), undefined);
|
|
47
|
+
assert.deepStrictEqual(inspect(parser, input('![ \\ a]{b}', new Context())), undefined);
|
|
44
48
|
assert.deepStrictEqual(inspect(parser, input('![\n]{b}', new Context())), undefined);
|
|
45
49
|
assert.deepStrictEqual(inspect(parser, input('![\\ ]{b}', new Context())), undefined);
|
|
46
50
|
assert.deepStrictEqual(inspect(parser, input('![\\\n]{b}', new Context())), undefined);
|
|
47
51
|
assert.deepStrictEqual(inspect(parser, input('![	]{b}', new Context())), undefined);
|
|
52
|
+
assert.deepStrictEqual(inspect(parser, input('![‍]{b}', new Context())), undefined);
|
|
48
53
|
assert.deepStrictEqual(inspect(parser, input('![&a;]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="&a;"></a>'], '']);
|
|
49
54
|
assert.deepStrictEqual(inspect(parser, input('![[]{b}', new Context())), undefined);
|
|
50
55
|
assert.deepStrictEqual(inspect(parser, input('![]]{b}', new Context())), undefined);
|
|
@@ -79,8 +84,6 @@ describe('Unit: parser/inline/media', () => {
|
|
|
79
84
|
assert.deepStrictEqual(inspect(parser, input('![]{?/../}', new Context())), [[`<a href="?/../" target="_blank"><img class="media" data-src="?/../" alt="?/../"></a>`], '']);
|
|
80
85
|
assert.deepStrictEqual(inspect(parser, input('![]{#/../}', new Context())), [[`<a href="#/../" target="_blank"><img class="media" data-src="#/../" alt="#/../"></a>`], '']);
|
|
81
86
|
assert.deepStrictEqual(inspect(parser, input('![]{^/b}', new Context())), [[`<a href="/b" target="_blank"><img class="media" data-src="/b" alt="^/b"></a>`], '']);
|
|
82
|
-
assert.deepStrictEqual(inspect(parser, input('![ a]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
|
|
83
|
-
assert.deepStrictEqual(inspect(parser, input('![ a ]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
|
|
84
87
|
assert.deepStrictEqual(inspect(parser, input('![a ]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
|
|
85
88
|
assert.deepStrictEqual(inspect(parser, input('![a ]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
|
|
86
89
|
assert.deepStrictEqual(inspect(parser, input('![a b]{c}', new Context())), [['<a href="c" target="_blank"><img class="media" data-src="c" alt="a b"></a>'], '']);
|