securemark 0.294.0 → 0.294.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 +8 -0
- package/design.md +27 -39
- package/dist/index.js +208 -180
- package/package.json +2 -2
- package/src/combinator/control/constraint/contract.ts +2 -2
- package/src/combinator/control/constraint/line.ts +2 -2
- package/src/combinator/control/manipulation/clear.ts +2 -2
- package/src/combinator/control/manipulation/indent.ts +3 -7
- package/src/combinator/control/manipulation/lazy.ts +1 -3
- package/src/combinator/control/manipulation/scope.ts +4 -6
- package/src/combinator/control/manipulation/surround.ts +5 -8
- package/src/combinator/control/monad/bind.ts +2 -3
- package/src/combinator/data/data.ts +38 -32
- package/src/combinator/data/parser/context.test.ts +4 -4
- package/src/combinator/data/parser/inits.ts +6 -8
- package/src/combinator/data/parser/sequence.test.ts +2 -2
- package/src/combinator/data/parser/sequence.ts +5 -7
- package/src/combinator/data/parser/some.test.ts +2 -2
- package/src/combinator/data/parser/some.ts +5 -7
- package/src/combinator/data/parser/subsequence.test.ts +2 -2
- package/src/combinator/data/parser/subsequence.ts +2 -2
- package/src/combinator/data/parser/tails.ts +2 -2
- package/src/combinator/data/parser/union.test.ts +2 -2
- package/src/combinator/data/parser/union.ts +2 -2
- package/src/combinator/data/parser.ts +36 -39
- package/src/debug.test.ts +2 -2
- package/src/parser/api/bind.ts +6 -6
- package/src/parser/api/header.ts +2 -2
- package/src/parser/api/normalize.ts +2 -2
- package/src/parser/api/parse.ts +11 -11
- package/src/parser/block/codeblock.ts +2 -2
- package/src/parser/block/extension/example.ts +2 -2
- package/src/parser/block/extension/figure.ts +1 -1
- package/src/parser/block/extension/message.ts +2 -2
- package/src/parser/block/extension/table.ts +2 -2
- package/src/parser/block.ts +5 -1
- package/src/parser/inline/autolink/url.test.ts +71 -72
- package/src/parser/inline/autolink/url.ts +4 -4
- package/src/parser/inline/emstrong.ts +5 -5
- package/src/parser/inline/reference.ts +2 -2
- package/src/parser/inline/ruby.ts +4 -4
- package/src/parser/processor/note.ts +2 -2
- package/src/parser/segment.ts +6 -12
- package/src/parser/source/line.ts +5 -0
- package/src/parser/source/str.ts +1 -1
- package/src/parser/util.ts +5 -4
- package/src/parser/visibility.ts +2 -2
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { List } from './data';
|
|
2
2
|
import { Delimiters } from './parser/context/delimiter';
|
|
3
3
|
|
|
4
|
-
export type Parser<N, C extends
|
|
5
|
-
= (input: Input<C
|
|
6
|
-
export interface Input<C extends
|
|
7
|
-
readonly context: C
|
|
4
|
+
export type Parser<N, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
5
|
+
= (input: Input<C>) => Result<N, C, D>;
|
|
6
|
+
export interface Input<C extends Ctx = Ctx> {
|
|
7
|
+
readonly context: C;
|
|
8
8
|
}
|
|
9
|
-
export type Result<N, C extends
|
|
10
|
-
=
|
|
11
|
-
| List<Data<N>>
|
|
9
|
+
export type Result<N, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
|
|
10
|
+
= List<Data<N>, C, D>
|
|
12
11
|
| undefined;
|
|
13
12
|
export { List };
|
|
14
13
|
export class Data<N> implements List.Node {
|
|
@@ -33,19 +32,44 @@ export interface CtxOptions {
|
|
|
33
32
|
// Objectの内部実装を利用する。
|
|
34
33
|
// 探索木を直接使用する場合は探索速度が重要で挿入は相対的に少なく削除は不要かつ不確実であるため
|
|
35
34
|
// AVL木が適当と思われる。
|
|
35
|
+
// 最大セグメントサイズ10KB内で探索コストが平均実行性能を圧迫するほど大きくなるとは考えにくいが
|
|
36
|
+
// 探索コストを減らすにはバックトラック位置数が規定数を超えた場合一定区間ごとに探索木を分割する方法が考えられる。
|
|
37
|
+
// 10KBの入力すべてを保持する探索木を1024文字ごとに分割するために必要なテーブルサイズは64bit*98=784byteとなる。
|
|
38
|
+
// 探索木のポインタによるオーバーヘッドを考慮すれば一定サイズ以上ではテーブルのほうが効率的となる。
|
|
39
|
+
// 区間別テーブルは固定サイズであるためプールして再使用できる。
|
|
40
|
+
// 従って分割時のデータ構造は区間ごとに探索木を動的に生成しデータ数に応じてテーブルに移行するのが最も効率的である。
|
|
41
|
+
// これにより最悪時間計算量線形化に要する最悪空間計算量が+1nに局限される。
|
|
42
|
+
// 木とテーブルいずれにおいてもバックトラックデータとオーバーヘッドを合わせた追加データサイズの最大値は
|
|
43
|
+
// セグメントサイズに制約されるため入力サイズに対する最大追加データサイズの平均比率はかなり小さくなる。
|
|
44
|
+
//
|
|
45
|
+
// 1. データ数が規定数を超えたら区間テーブルを生成しデータを振り分ける。
|
|
46
|
+
// - 子ノードのポインタだけ保持するとしても1ノード複数データ保持で圧縮できるかは微妙。
|
|
47
|
+
// - 1ノードに2データ保持すれば2連続データを1/2の確率で捕捉し1バックトラックあたりの平均追加データサイズは
|
|
48
|
+
// -7byte(((16+1)*2-(16+2))*2+((16+1)*2-(16+2)*2)*2)/4=(32-4)/4=7の10byteに減少する。
|
|
49
|
+
// 2連続データの発生確率が1/5なら-3.2byteの13.8byte、1/10なら+0.4byteの17.4byteに増加する。
|
|
50
|
+
// - 1ノードに4データ保持すれば2連続データを3/4の確率で捕捉し1バックトラックあたりの平均追加データサイズは
|
|
51
|
+
// -9byte(((16+1)*2-(16+4))*3+((16+1)*2-(16+4)*2))/4=(42-6)/4=9の8byteに減少する。
|
|
52
|
+
// 2連続データの発生確率が1/5なら-3.6byteの13.4byte、1/10なら+1.2byteの18.2byteに増加する。
|
|
53
|
+
// 2. 区間内のデータ構造は探索木から開始しデータ数が規定数を超えたらテーブルに変換する。
|
|
54
|
+
// - 1ノード1データ1区間1024文字ならば1024<(64/8*2+1)*61から1区間61データ以上でテーブルのほうが小さくなる。
|
|
55
|
+
// - 64/8*2+1=17文字に1か所以下のバックトラックでテーブル以上の効率となる。
|
|
56
|
+
// - 通常の入力でバックトラックが17文字に平均1か所以上となることは考えられず
|
|
57
|
+
// 1段落数百文字あたり平均2、3か所以下が妥当な頻度でありこの場合の最大追加データサイズは
|
|
58
|
+
// 入力内の最大セグメントサイズの10%前後である。
|
|
59
|
+
//
|
|
36
60
|
backtracks?: Record<number, number>;
|
|
37
61
|
linebreak?: number;
|
|
38
62
|
range?: number;
|
|
39
63
|
}
|
|
40
64
|
export type Node<P extends Parser<unknown>> = P extends Parser<infer N> ? N : never;
|
|
41
|
-
export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown,
|
|
42
|
-
export type Context<P extends Parser<unknown>> = P extends Parser<unknown, infer C> ? C
|
|
65
|
+
export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown, Ctx, infer D> ? D : never;
|
|
66
|
+
export type Context<P extends Parser<unknown>> = P extends Parser<unknown, infer C> ? C : never;
|
|
43
67
|
export type SubNode<P extends Parser<unknown>> = ExtractSubNode<SubParsers<P>>;
|
|
44
68
|
export type IntermediateParser<P extends Parser<unknown>> = Parser<SubNode<P>, Context<P>, SubParsers<P>>;
|
|
45
69
|
type ExtractSubNode<D extends Parser<unknown>[]> = ExtractSubParser<D> extends infer N ? N extends Parser<infer U> ? U : never : never;
|
|
46
70
|
type ExtractSubParser<D extends Parser<unknown>[]> = D extends (infer P)[] ? P extends Parser<unknown> ? P : never : never;
|
|
47
71
|
|
|
48
|
-
export function input(source: string, context:
|
|
72
|
+
export function input<C extends CtxOptions>(source: string, context: C): Input<C & Ctx> {
|
|
49
73
|
// @ts-expect-error
|
|
50
74
|
context.source = source;
|
|
51
75
|
// @ts-expect-error
|
|
@@ -68,38 +92,11 @@ export function subinput<C extends Ctx>(source: string, context: C): Input<C> {
|
|
|
68
92
|
};
|
|
69
93
|
}
|
|
70
94
|
|
|
71
|
-
export function clean<C extends Ctx>(context: C): C {
|
|
72
|
-
const { source, position } = context;
|
|
73
|
-
for (const p of Object.keys(context)) {
|
|
74
|
-
context[p] = undefined;
|
|
75
|
-
}
|
|
76
|
-
context.source = source;
|
|
77
|
-
context.position = position;
|
|
78
|
-
return context;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export { eval_ as eval };
|
|
82
|
-
function eval_<N>(result: NonNullable<Result<N>>, default_?: List<Data<N>>): List<Data<N>>;
|
|
83
|
-
function eval_<N>(result: Result<N>, default_: List<Data<N>>): List<Data<N>>;
|
|
84
|
-
function eval_<N>(result: Result<N>, default_?: undefined): List<Data<N>> | undefined;
|
|
85
|
-
function eval_<N>(result: Result<N>, default_?: List<Data<N>>): List<Data<N>> | undefined {
|
|
86
|
-
assert(!Array.isArray(result));
|
|
87
|
-
return result
|
|
88
|
-
? result as List<Data<N>>
|
|
89
|
-
: default_;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
95
|
export function failsafe<P extends Parser<unknown>>(parser: P): P;
|
|
93
96
|
export function failsafe<N>(parser: Parser<N>): Parser<N> {
|
|
94
97
|
assert(parser);
|
|
95
98
|
return input => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const result = parser(input);
|
|
99
|
-
if (result === undefined) {
|
|
100
|
-
context.source = source;
|
|
101
|
-
context.position = position;
|
|
102
|
-
}
|
|
103
|
-
return result;
|
|
99
|
+
const position = input.context.position;
|
|
100
|
+
return parser(input) ?? (input.context.position = position, undefined);
|
|
104
101
|
};
|
|
105
102
|
}
|
package/src/debug.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Result, Ctx
|
|
1
|
+
import { Result, Ctx } from './combinator/data/parser';
|
|
2
2
|
import { html, define } from 'typed-dom/dom';
|
|
3
3
|
import { querySelectorWith, querySelectorAllWith } from 'typed-dom/query';
|
|
4
4
|
|
|
5
5
|
export function inspect(result: Result<DocumentFragment | HTMLElement | string>, ctx: Ctx, until: number | string = Infinity): [string[], string] | undefined {
|
|
6
6
|
return result && [
|
|
7
|
-
|
|
7
|
+
result.foldl<string[]>((acc, { value: node }) => {
|
|
8
8
|
assert(node);
|
|
9
9
|
if (typeof node === 'string') return acc.push(node), acc;
|
|
10
10
|
if (node instanceof DocumentFragment) return acc.push(html('div', [node]).innerHTML), acc;
|
package/src/parser/api/bind.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParserSettings, Progress } from '../../..';
|
|
2
2
|
import { MarkdownParser } from '../../../markdown';
|
|
3
|
-
import { input
|
|
3
|
+
import { input } from '../../combinator/data/parser';
|
|
4
4
|
import { segment } from '../segment';
|
|
5
5
|
import { header } from '../header';
|
|
6
6
|
import { block } from '../block';
|
|
@@ -19,7 +19,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
19
19
|
nearest: (position: number) => HTMLElement | undefined;
|
|
20
20
|
index: (block: HTMLElement) => number;
|
|
21
21
|
} {
|
|
22
|
-
|
|
22
|
+
const context: MarkdownParser.Options = {
|
|
23
23
|
...settings,
|
|
24
24
|
host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
25
25
|
};
|
|
@@ -75,7 +75,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
75
75
|
for (; index < sourceSegments.length - last; ++index) {
|
|
76
76
|
assert(rev === revision);
|
|
77
77
|
const seg = sourceSegments[index];
|
|
78
|
-
const es =
|
|
78
|
+
const es = (header(input(seg, { header: index === 0 } as MarkdownParser.Options)) || block(input(seg, context)))
|
|
79
79
|
?.foldl<HTMLElement[]>((acc, { value }) => void acc.push(value) || acc, []) ?? [];
|
|
80
80
|
blocks.splice(index, 0, [seg, es, url]);
|
|
81
81
|
if (es.length === 0) continue;
|
|
@@ -83,7 +83,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
83
83
|
// Therefore any `base` node will never be unavailable by deletions until all the dependent `el` nodes are added.
|
|
84
84
|
push(adds, es.map(el => [el, base] as const));
|
|
85
85
|
adds.reverse();
|
|
86
|
-
|
|
86
|
+
for (; adds.length > 0;) {
|
|
87
87
|
assert(rev === revision);
|
|
88
88
|
const [el, base] = adds.pop()!;
|
|
89
89
|
target.insertBefore(el, base);
|
|
@@ -100,7 +100,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
100
100
|
}
|
|
101
101
|
assert(blocks.length === sourceSegments.length);
|
|
102
102
|
adds.reverse();
|
|
103
|
-
|
|
103
|
+
for (; adds.length > 0;) {
|
|
104
104
|
assert(rev === revision);
|
|
105
105
|
const [el, base] = adds.pop()!;
|
|
106
106
|
target.insertBefore(el, base);
|
|
@@ -109,7 +109,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
|
|
|
109
109
|
if (rev !== revision) return yield { type: 'cancel' };
|
|
110
110
|
}
|
|
111
111
|
dels.reverse();
|
|
112
|
-
|
|
112
|
+
for (; dels.length > 0;) {
|
|
113
113
|
assert(rev === revision);
|
|
114
114
|
const [el] = dels.pop()!;
|
|
115
115
|
el.parentNode?.removeChild(el);
|
package/src/parser/api/header.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { input
|
|
1
|
+
import { input } from '../../combinator/data/parser';
|
|
2
2
|
import { header as h } from '../header';
|
|
3
3
|
|
|
4
4
|
export function header(source: string): string {
|
|
@@ -14,7 +14,7 @@ export function headers(source: string): string[] {
|
|
|
14
14
|
function parse(source: string): [HTMLElement, number] | [] {
|
|
15
15
|
const i = input(source, {});
|
|
16
16
|
const result = h(i);
|
|
17
|
-
const el =
|
|
17
|
+
const el = result?.head?.value;
|
|
18
18
|
return el?.tagName === 'ASIDE'
|
|
19
19
|
? [el, i.context.position]
|
|
20
20
|
: [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { input
|
|
1
|
+
import { input } from '../../combinator/data/parser';
|
|
2
2
|
import { unsafehtmlentity } from '../inline/htmlentity';
|
|
3
3
|
|
|
4
4
|
const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
|
|
@@ -60,7 +60,7 @@ export const invisibleHTMLEntityNames = [
|
|
|
60
60
|
] as const;
|
|
61
61
|
const unreadableHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames.slice(2);
|
|
62
62
|
const unreadableEscapableCharacters = unreadableHTMLEntityNames
|
|
63
|
-
.map(name =>
|
|
63
|
+
.map(name => unsafehtmlentity(input(`&${name};`, {}))!.head!.value);
|
|
64
64
|
assert(unreadableEscapableCharacters.length === unreadableHTMLEntityNames.length);
|
|
65
65
|
assert(unreadableEscapableCharacters.every(c => c.length === 1));
|
|
66
66
|
const unreadableEscapableCharacter = new RegExp(`[${unreadableEscapableCharacters.join('')}]`, 'g');
|
package/src/parser/api/parse.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParserOptions } from '../../..';
|
|
2
2
|
import { MarkdownParser } from '../../../markdown';
|
|
3
|
-
import { input
|
|
3
|
+
import { input } from '../../combinator/data/parser';
|
|
4
4
|
import { segment } from '../segment';
|
|
5
5
|
import { header } from '../header';
|
|
6
6
|
import { block } from '../block';
|
|
@@ -15,13 +15,13 @@ interface Options extends ParserOptions {
|
|
|
15
15
|
readonly test?: boolean;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function parse(source: string,
|
|
18
|
+
export function parse(source: string, options: Options = {}, context?: MarkdownParser.Options): DocumentFragment {
|
|
19
19
|
const url = headers(source).find(field => field.toLowerCase().startsWith('url:'))?.slice(4).trim() ?? '';
|
|
20
20
|
source = !context ? normalize(source) : source;
|
|
21
21
|
context = {
|
|
22
|
-
host:
|
|
22
|
+
host: options.host ?? context?.host ?? new ReadonlyURL(location.pathname, location.origin),
|
|
23
23
|
url: url ? new ReadonlyURL(url as ':') : context?.url,
|
|
24
|
-
id:
|
|
24
|
+
id: options.id ?? context?.id,
|
|
25
25
|
caches: context?.caches,
|
|
26
26
|
resources: context?.resources,
|
|
27
27
|
};
|
|
@@ -35,14 +35,14 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
|
|
|
35
35
|
let index = 0;
|
|
36
36
|
for (const seg of segment(source)) {
|
|
37
37
|
node.append(
|
|
38
|
-
...
|
|
38
|
+
...(header(input(seg, { header: index++ === 0 } as MarkdownParser.Context)) || block(input(seg, context)))
|
|
39
39
|
?.foldl<HTMLElement[]>((acc, { value }) => void acc.push(value) || acc, []) ?? []);
|
|
40
40
|
}
|
|
41
|
-
assert(
|
|
42
|
-
if (
|
|
43
|
-
for (const _ of figure(node,
|
|
44
|
-
for (const _ of note(node,
|
|
45
|
-
assert(
|
|
46
|
-
assert(
|
|
41
|
+
assert(options.id !== '' || !node.querySelector('[id], .index[href], .label[href], .annotation > a[href], .reference > a[href]'));
|
|
42
|
+
if (options.test) return node;
|
|
43
|
+
for (const _ of figure(node, options.notes, context));
|
|
44
|
+
for (const _ of note(node, options.notes, context));
|
|
45
|
+
assert(options.id !== '' || !node.querySelector('[id], .index[href], .label[href], .annotation > a[href], .reference > a[href]'));
|
|
46
|
+
assert(options.id !== '' || !options.notes?.references.querySelector('[id], .index[href], .label[href]'));
|
|
47
47
|
return node;
|
|
48
48
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CodeBlockParser } from '../block';
|
|
2
|
-
import { List, Data, subinput
|
|
2
|
+
import { List, Data, subinput } from '../../combinator/data/parser';
|
|
3
3
|
import { block, fence, clear, fmap } from '../../combinator';
|
|
4
4
|
import { autolink } from '../autolink';
|
|
5
5
|
import { unwrap, invalid } from '../util';
|
|
@@ -73,6 +73,6 @@ export const codeblock: CodeBlockParser = block(fmap(
|
|
|
73
73
|
params.lang
|
|
74
74
|
? context.caches?.code?.get(`${params.lang ?? ''}\n${body.slice(0, -1)}`)?.cloneNode(true).childNodes ||
|
|
75
75
|
body.slice(0, -1) || undefined
|
|
76
|
-
: defrag(unwrap(
|
|
76
|
+
: defrag(unwrap(autolink(subinput(body.slice(0, -1), context)))));
|
|
77
77
|
return new List([new Data(el)]);
|
|
78
78
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ExtensionParser } from '../../block';
|
|
2
2
|
import { Recursion } from '../../context';
|
|
3
|
-
import { List, Data, subinput
|
|
3
|
+
import { List, Data, subinput } from '../../../combinator/data/parser';
|
|
4
4
|
import { recursion, block, fence, fmap } from '../../../combinator';
|
|
5
5
|
import { mathblock } from '../mathblock';
|
|
6
6
|
import { unwrap, invalid } from '../../util';
|
|
@@ -49,7 +49,7 @@ export const example: ExtensionParser.ExampleParser = recursion(Recursion.block,
|
|
|
49
49
|
new Data(html('aside', { class: 'example', 'data-type': 'math' }, [
|
|
50
50
|
html('pre', { translate: 'no' }, body.slice(0, -1)),
|
|
51
51
|
html('hr'),
|
|
52
|
-
|
|
52
|
+
mathblock(subinput(`$$\n${body}$$`, context))!.head!.value,
|
|
53
53
|
])),
|
|
54
54
|
]);
|
|
55
55
|
default:
|
|
@@ -44,7 +44,7 @@ export const segment: FigureParser.SegmentParser = block(match(
|
|
|
44
44
|
]),
|
|
45
45
|
]),
|
|
46
46
|
closer),
|
|
47
|
-
([, fence]) => fence.length,
|
|
47
|
+
([, fence]) => fence.length <= 16 ? fence.length : -1, [])));
|
|
48
48
|
|
|
49
49
|
export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
|
|
50
50
|
convert(source => source.slice(source.match(/^~+(?:\w+\s+)?/)![0].length, source.trimEnd().lastIndexOf('\n')),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExtensionParser } from '../../block';
|
|
2
|
-
import { List, Data, subinput
|
|
2
|
+
import { List, Data, subinput } from '../../../combinator/data/parser';
|
|
3
3
|
import { union, block, fence, fmap } from '../../../combinator';
|
|
4
4
|
import { segment } from '../../segment';
|
|
5
5
|
import { emptyline } from '../../source';
|
|
@@ -58,7 +58,7 @@ export const message: MessageParser = block(fmap(
|
|
|
58
58
|
},
|
|
59
59
|
[...segment(body)].reduce(
|
|
60
60
|
(acc, seg) =>
|
|
61
|
-
push(acc, unwrap(
|
|
61
|
+
push(acc, unwrap(content(subinput(seg, context)))),
|
|
62
62
|
[html('h1', title(type))])))
|
|
63
63
|
]);
|
|
64
64
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { max, min } from 'spica/alias';
|
|
2
2
|
import { ExtensionParser } from '../../block';
|
|
3
|
-
import { List, Data, subinput
|
|
3
|
+
import { List, Data, subinput } from '../../../combinator/data/parser';
|
|
4
4
|
import { union, subsequence, inits, some, block, line, validate, fence, rewrite, clear, surround, open, convert, dup, lazy, fmap } from '../../../combinator';
|
|
5
5
|
import { inline, medialink, media, lineshortmedia } from '../../inline';
|
|
6
6
|
import { str, anyline, emptyline, contentline } from '../../source';
|
|
@@ -42,7 +42,7 @@ export const table: TableParser = block(fmap(
|
|
|
42
42
|
switch (type) {
|
|
43
43
|
case 'grid':
|
|
44
44
|
case undefined:
|
|
45
|
-
return (
|
|
45
|
+
return (parser(subinput(body, context)) ?? new List([new Data(html('table'))]))
|
|
46
46
|
.foldl(
|
|
47
47
|
(acc, { value }) => acc.push(new Data(define(value, { 'data-type': type }))) && acc,
|
|
48
48
|
new List());
|
package/src/parser/block.ts
CHANGED
|
@@ -56,11 +56,16 @@ export const block: BlockParser = reset(
|
|
|
56
56
|
backtracks: {},
|
|
57
57
|
},
|
|
58
58
|
error(union([
|
|
59
|
+
emptyline,
|
|
59
60
|
input => {
|
|
60
61
|
const { context: { source, position } } = input;
|
|
61
62
|
if (position === source.length) return;
|
|
62
63
|
const fst = source[position];
|
|
63
64
|
switch (fst) {
|
|
65
|
+
case '\n':
|
|
66
|
+
assert(source.trim() === '');
|
|
67
|
+
input.context.position = source.length;
|
|
68
|
+
return new List();
|
|
64
69
|
case '=':
|
|
65
70
|
if (source.startsWith('===', position)) return pagebreak(input);
|
|
66
71
|
break;
|
|
@@ -105,7 +110,6 @@ export const block: BlockParser = reset(
|
|
|
105
110
|
if ('0' <= fst && fst <= '9') return olist(input);
|
|
106
111
|
}
|
|
107
112
|
},
|
|
108
|
-
emptyline,
|
|
109
113
|
paragraph
|
|
110
114
|
]) as any));
|
|
111
115
|
|
|
@@ -1,97 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { autolink } from '../autolink';
|
|
2
2
|
import { some } from '../../../combinator';
|
|
3
3
|
import { input } from '../../../combinator/data/parser';
|
|
4
4
|
import { inspect } from '../../../debug.test';
|
|
5
5
|
|
|
6
6
|
describe('Unit: parser/inline/autolink/url', () => {
|
|
7
7
|
describe('url', () => {
|
|
8
|
-
const parser = (source: string) => some(
|
|
8
|
+
const parser = (source: string) => some(autolink)((i => void ++i.context.position || i)(input(source, ctx)));
|
|
9
9
|
const { context: ctx } = input('', {});
|
|
10
10
|
|
|
11
11
|
it('invalid', () => {
|
|
12
12
|
assert.deepStrictEqual(inspect(parser(''), ctx), undefined);
|
|
13
|
-
assert.deepStrictEqual(inspect(parser(' http'), ctx), [['
|
|
14
|
-
assert.deepStrictEqual(inspect(parser(' ttp'), ctx), [['
|
|
15
|
-
assert.deepStrictEqual(inspect(parser(' http://'), ctx), [['
|
|
16
|
-
assert.deepStrictEqual(inspect(parser(' http://['), ctx), [['
|
|
17
|
-
assert.deepStrictEqual(inspect(parser(' http://]'), ctx), [['
|
|
18
|
-
assert.deepStrictEqual(inspect(parser(' Http://host'), ctx), [['
|
|
19
|
-
assert.deepStrictEqual(inspect(parser(' http://[::ffff:0:0%1]'), ctx), [['
|
|
20
|
-
assert.deepStrictEqual(inspect(parser(' http://[::ffff:0:0/96]'), ctx), [['
|
|
13
|
+
assert.deepStrictEqual(inspect(parser(' http'), ctx), [['http'], '']);
|
|
14
|
+
assert.deepStrictEqual(inspect(parser(' ttp'), ctx), [['ttp'], '']);
|
|
15
|
+
assert.deepStrictEqual(inspect(parser(' http://'), ctx), [['http:'], '//']);
|
|
16
|
+
assert.deepStrictEqual(inspect(parser(' http://['), ctx), [['http:'], '//[']);
|
|
17
|
+
assert.deepStrictEqual(inspect(parser(' http://]'), ctx), [['http:'], '//]']);
|
|
18
|
+
assert.deepStrictEqual(inspect(parser(' Http://host'), ctx), [['Http:'], '//host']);
|
|
19
|
+
assert.deepStrictEqual(inspect(parser(' http://[::ffff:0:0%1]'), ctx), [['<a class="invalid">http://[::ffff:0:0%1]</a>'], '']);
|
|
20
|
+
assert.deepStrictEqual(inspect(parser(' http://[::ffff:0:0/96]'), ctx), [['<a class="invalid">http://[::ffff:0:0/96]</a>'], '']);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('basic', () => {
|
|
24
|
-
assert.deepStrictEqual(inspect(parser(' http://a'), ctx), [['
|
|
25
|
-
assert.deepStrictEqual(inspect(parser(' http://a/'), ctx), [['
|
|
26
|
-
assert.deepStrictEqual(inspect(parser(' http://a:80'), ctx), [['
|
|
27
|
-
assert.deepStrictEqual(inspect(parser(' http://a.b'), ctx), [['
|
|
28
|
-
assert.deepStrictEqual(inspect(parser(` http://a?#${encodeURIComponent(':/[]()<>?#=& ')}`), ctx), [['
|
|
29
|
-
assert.deepStrictEqual(inspect(parser(' http://a#()'), ctx), [['
|
|
30
|
-
assert.deepStrictEqual(inspect(parser(' http://a#( )'), ctx), [['
|
|
31
|
-
assert.deepStrictEqual(inspect(parser(' http://a#(\n)'), ctx), [['
|
|
32
|
-
assert.deepStrictEqual(inspect(parser(' http://[::]'), ctx), [['
|
|
33
|
-
assert.deepStrictEqual(inspect(parser('
|
|
34
|
-
assert.deepStrictEqual(inspect(parser('
|
|
24
|
+
assert.deepStrictEqual(inspect(parser(' http://a'), ctx), [['<a class="url" href="http://a" target="_blank">http://a</a>'], '']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser(' http://a/'), ctx), [['<a class="url" href="http://a/" target="_blank">http://a/</a>'], '']);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser(' http://a:80'), ctx), [['<a class="url" href="http://a:80" target="_blank">http://a:80</a>'], '']);
|
|
27
|
+
assert.deepStrictEqual(inspect(parser(' http://a.b'), ctx), [['<a class="url" href="http://a.b" target="_blank">http://a.b</a>'], '']);
|
|
28
|
+
assert.deepStrictEqual(inspect(parser(` http://a?#${encodeURIComponent(':/[]()<>?#=& ')}`), ctx), [['<a class="url" href="http://a?#%3A%2F%5B%5D()%3C%3E%3F%23%3D%26%20" target="_blank">http://a?#%3A%2F[]()<>%3F%23%3D%26%20</a>'], '']);
|
|
29
|
+
assert.deepStrictEqual(inspect(parser(' http://a#()'), ctx), [['<a class="url" href="http://a#()" target="_blank">http://a#()</a>'], '']);
|
|
30
|
+
assert.deepStrictEqual(inspect(parser(' http://a#( )'), ctx), [['<a class="url" href="http://a#" target="_blank">http://a#</a>'], '( )']);
|
|
31
|
+
assert.deepStrictEqual(inspect(parser(' http://a#(\n)'), ctx), [['<a class="url" href="http://a#" target="_blank">http://a#</a>'], '(\n)']);
|
|
32
|
+
assert.deepStrictEqual(inspect(parser(' http://[::]'), ctx), [['<a class="url" href="http://[::]" target="_blank">http://[::]</a>'], '']);
|
|
33
|
+
assert.deepStrictEqual(inspect(parser('\rhttp://a#\\'), ctx), [['<a class="url" href="http://a#\\" target="_blank">http://a#\\</a>'], '']);
|
|
34
|
+
assert.deepStrictEqual(inspect(parser('\nhttp://a#\\'), ctx), [['<a class="url" href="http://a#\\" target="_blank">http://a#\\</a>'], '']);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('trailing symbols', () => {
|
|
38
|
-
assert.deepStrictEqual(inspect(parser(' http://host '), ctx), [['
|
|
39
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
40
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
41
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
42
|
-
assert.deepStrictEqual(inspect(parser(' http://host\\'), ctx), [['
|
|
43
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
44
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
45
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
46
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
47
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
48
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
49
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
50
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
51
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
52
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
53
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
54
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
55
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
56
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
57
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
58
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
59
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
60
|
-
assert.deepStrictEqual(inspect(parser(' http://
|
|
61
|
-
assert.deepStrictEqual(inspect(parser(' http://
|
|
62
|
-
assert.deepStrictEqual(inspect(parser(' http://host
|
|
63
|
-
assert.deepStrictEqual(inspect(parser(` http://host'`), ctx), [['
|
|
64
|
-
assert.deepStrictEqual(inspect(parser(' http://host"'), ctx), [['
|
|
65
|
-
assert.deepStrictEqual(inspect(parser(' http://host`'), ctx), [['
|
|
66
|
-
assert.deepStrictEqual(inspect(parser(' http://host|'), ctx), [['
|
|
67
|
-
assert.deepStrictEqual(inspect(parser(' http://host&'), ctx), [['
|
|
68
|
-
assert.deepStrictEqual(inspect(parser(' http://host$'), ctx), [['
|
|
69
|
-
assert.deepStrictEqual(inspect(parser(' http://host#"$"'), ctx), [['
|
|
70
|
-
assert.deepStrictEqual(inspect(parser(' http://host#($)'), ctx), [['
|
|
71
|
-
assert.deepStrictEqual(inspect(parser(' http://host#(($))'), ctx), [['
|
|
72
|
-
assert.deepStrictEqual(inspect(parser(' http://user@host'), ctx), [['
|
|
73
|
-
assert.deepStrictEqual(inspect(parser(' http://host#@'), ctx), [['
|
|
74
|
-
assert.deepStrictEqual(inspect(parser(' http://host['), ctx), [['
|
|
75
|
-
assert.deepStrictEqual(inspect(parser(' http://host]'), ctx), [['
|
|
76
|
-
assert.deepStrictEqual(inspect(parser(' http://host('), ctx), [['
|
|
77
|
-
assert.deepStrictEqual(inspect(parser(' http://host)'), ctx), [['
|
|
78
|
-
assert.deepStrictEqual(inspect(parser(' http://host{'), ctx), [['
|
|
79
|
-
assert.deepStrictEqual(inspect(parser(' http://host}'), ctx), [['
|
|
80
|
-
assert.deepStrictEqual(inspect(parser(' http://host<'), ctx), [['
|
|
81
|
-
assert.deepStrictEqual(inspect(parser(' http://host>'), ctx), [['
|
|
82
|
-
assert.deepStrictEqual(inspect(parser(' http://host('), ctx), [['
|
|
83
|
-
assert.deepStrictEqual(inspect(parser(' http://host)'), ctx), [['
|
|
84
|
-
assert.deepStrictEqual(inspect(parser(' http://host\\"'), ctx), [[' ', '<a class="url" href="http://host" target="_blank">http://host</a>', '"'], '']);
|
|
38
|
+
assert.deepStrictEqual(inspect(parser(' http://host '), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ' ']);
|
|
39
|
+
assert.deepStrictEqual(inspect(parser(' http://host\n'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '\n']);
|
|
40
|
+
assert.deepStrictEqual(inspect(parser(' http://host\\'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '\\']);
|
|
41
|
+
assert.deepStrictEqual(inspect(parser(' http://host\\a'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '\\a']);
|
|
42
|
+
assert.deepStrictEqual(inspect(parser(' http://host\\ '), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '\\ ']);
|
|
43
|
+
assert.deepStrictEqual(inspect(parser(' http://host\\\n'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '\\\n']);
|
|
44
|
+
assert.deepStrictEqual(inspect(parser(' http://host. '), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '. ']);
|
|
45
|
+
assert.deepStrictEqual(inspect(parser(' http://host.\n'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '.\n']);
|
|
46
|
+
assert.deepStrictEqual(inspect(parser(' http://host.\\'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '.\\']);
|
|
47
|
+
assert.deepStrictEqual(inspect(parser(' http://host.\\ '), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '.\\ ']);
|
|
48
|
+
assert.deepStrictEqual(inspect(parser(' http://host.\\\n'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '.\\\n']);
|
|
49
|
+
assert.deepStrictEqual(inspect(parser(' http://host,'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ',']);
|
|
50
|
+
assert.deepStrictEqual(inspect(parser(' http://host;'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ';']);
|
|
51
|
+
assert.deepStrictEqual(inspect(parser(' http://host.'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '.']);
|
|
52
|
+
assert.deepStrictEqual(inspect(parser(' http://host:'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ':']);
|
|
53
|
+
assert.deepStrictEqual(inspect(parser(' http://host!'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '!']);
|
|
54
|
+
assert.deepStrictEqual(inspect(parser(' http://host?'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '?']);
|
|
55
|
+
assert.deepStrictEqual(inspect(parser(' http://host+'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '+']);
|
|
56
|
+
assert.deepStrictEqual(inspect(parser(' http://host-'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '-']);
|
|
57
|
+
assert.deepStrictEqual(inspect(parser(' http://host*'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '*']);
|
|
58
|
+
assert.deepStrictEqual(inspect(parser(' http://host='), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '=']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser(' http://host~'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '~']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser(' http://host^'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '^']);
|
|
61
|
+
assert.deepStrictEqual(inspect(parser(' http://host_'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '_']);
|
|
62
|
+
assert.deepStrictEqual(inspect(parser(' http://host/'), ctx), [['<a class="url" href="http://host/" target="_blank">http://host/</a>'], '']);
|
|
63
|
+
assert.deepStrictEqual(inspect(parser(` http://host'`), ctx), [['<a class="url" href="http://host\'" target="_blank">http://host\'</a>'], '']);
|
|
64
|
+
assert.deepStrictEqual(inspect(parser(' http://host"'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '"']);
|
|
65
|
+
assert.deepStrictEqual(inspect(parser(' http://host`'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '`']);
|
|
66
|
+
assert.deepStrictEqual(inspect(parser(' http://host|'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '|']);
|
|
67
|
+
assert.deepStrictEqual(inspect(parser(' http://host&'), ctx), [['<a class="url" href="http://host&" target="_blank">http://host&</a>'], '']);
|
|
68
|
+
assert.deepStrictEqual(inspect(parser(' http://host$'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '$']);
|
|
69
|
+
assert.deepStrictEqual(inspect(parser(' http://host#"$"'), ctx), [['<a class="url" href="http://host#" target="_blank">http://host#</a>'], '"$"']);
|
|
70
|
+
assert.deepStrictEqual(inspect(parser(' http://host#($)'), ctx), [['<a class="url" href="http://host#" target="_blank">http://host#</a>'], '($)']);
|
|
71
|
+
assert.deepStrictEqual(inspect(parser(' http://host#(($))'), ctx), [['<a class="url" href="http://host#" target="_blank">http://host#</a>'], '(($))']);
|
|
72
|
+
assert.deepStrictEqual(inspect(parser(' http://user@host'), ctx), [['<a class="url" href="http://user@host" target="_blank">http://user@host</a>'], '']);
|
|
73
|
+
assert.deepStrictEqual(inspect(parser(' http://host#@'), ctx), [['<a class="url" href="http://host#@" target="_blank">http://host#@</a>'], '']);
|
|
74
|
+
assert.deepStrictEqual(inspect(parser(' http://host['), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '[']);
|
|
75
|
+
assert.deepStrictEqual(inspect(parser(' http://host]'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ']']);
|
|
76
|
+
assert.deepStrictEqual(inspect(parser(' http://host('), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '(']);
|
|
77
|
+
assert.deepStrictEqual(inspect(parser(' http://host)'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ')']);
|
|
78
|
+
assert.deepStrictEqual(inspect(parser(' http://host{'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '{']);
|
|
79
|
+
assert.deepStrictEqual(inspect(parser(' http://host}'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '}']);
|
|
80
|
+
assert.deepStrictEqual(inspect(parser(' http://host<'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '<']);
|
|
81
|
+
assert.deepStrictEqual(inspect(parser(' http://host>'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '>']);
|
|
82
|
+
assert.deepStrictEqual(inspect(parser(' http://host('), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '(']);
|
|
83
|
+
assert.deepStrictEqual(inspect(parser(' http://host)'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], ')']);
|
|
85
84
|
});
|
|
86
85
|
|
|
87
86
|
it('trailing entities', () => {
|
|
88
|
-
assert.deepStrictEqual(inspect(parser(' http://host?&hl;'), ctx), [['
|
|
89
|
-
assert.deepStrictEqual(inspect(parser(' http://host?&hl;&hl;'), ctx), [['
|
|
87
|
+
assert.deepStrictEqual(inspect(parser(' http://host?&hl;'), ctx), [['<a class="url" href="http://host?&hl" target="_blank">http://host?&hl</a>'], ';']);
|
|
88
|
+
assert.deepStrictEqual(inspect(parser(' http://host?&hl;&hl;'), ctx), [['<a class="url" href="http://host?&hl;&hl" target="_blank">http://host?&hl;&hl</a>'], ';']);
|
|
90
89
|
});
|
|
91
90
|
|
|
92
91
|
it('protocol', () => {
|
|
93
|
-
assert.deepStrictEqual(inspect(parser(' http://host'), ctx), [['
|
|
94
|
-
assert.deepStrictEqual(inspect(parser(' https://host'), ctx), [['
|
|
92
|
+
assert.deepStrictEqual(inspect(parser(' http://host'), ctx), [['<a class="url" href="http://host" target="_blank">http://host</a>'], '']);
|
|
93
|
+
assert.deepStrictEqual(inspect(parser(' https://host'), ctx), [['<a class="url" href="https://host" target="_blank">https://host</a>'], '']);
|
|
95
94
|
});
|
|
96
95
|
|
|
97
96
|
});
|
|
@@ -7,10 +7,10 @@ import { unescsource, str } from '../../source';
|
|
|
7
7
|
|
|
8
8
|
export const url: AutolinkParser.UrlParser = lazy(() => rewrite(
|
|
9
9
|
open(
|
|
10
|
-
/(?<![0-
|
|
11
|
-
precedence(
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/(?<![0-9A-Za-z][.+-]?)https?:\/\/(?=[\x21-\x7E])/y,
|
|
11
|
+
precedence(0, some(union([
|
|
12
|
+
some(unescsource, /(?<![-+*=~^_,.;:!?])[-+*=~^_,.;:!?]*(?=[\\$"`\[\](){}<>()[]{}|]|[^\x21-\x7E]|$)/y),
|
|
13
|
+
precedence(1, verify(bracket, ns => ns.length > 0)),
|
|
14
14
|
]), undefined, [[/[^\x21-\x7E]|\$/y, 9]])),
|
|
15
15
|
false,
|
|
16
16
|
[3 | Backtrack.autolink]),
|