securemark 0.299.4 → 0.300.1
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 +0 -6
- package/dist/index.js +2847 -1993
- package/index.d.ts +2 -1
- package/markdown.d.ts +209 -183
- package/package.json +1 -1
- package/src/api/bind.test.ts +0 -22
- package/src/api/bind.ts +15 -10
- package/src/api/header.ts +8 -5
- package/src/api/parse.test.ts +118 -122
- package/src/api/parse.ts +13 -31
- package/src/api/run.ts +6 -0
- package/src/api.ts +1 -0
- package/src/combinator/control/inits.ts +35 -0
- package/src/combinator/control/sequence.test.ts +38 -0
- package/src/combinator/control/sequence.ts +19 -0
- package/src/combinator/control/some.test.ts +41 -0
- package/src/combinator/{data/parser → control}/some.ts +39 -26
- package/src/combinator/control/state.ts +33 -0
- package/src/combinator/control/subsequence.test.ts +47 -0
- package/src/combinator/control/subsequence.ts +16 -0
- package/src/combinator/control/tails.ts +8 -0
- package/src/combinator/control/union.test.ts +37 -0
- package/src/combinator/control/union.ts +31 -0
- package/src/combinator/{data/delimiter.ts → delimiter.ts} +40 -60
- package/src/combinator/effect/backtrack.ts +64 -0
- package/src/combinator/effect/clock.ts +10 -0
- package/src/combinator/effect/precedence.ts +50 -0
- package/src/combinator/effect/recursion.ts +30 -0
- package/src/combinator/effect/scope.ts +100 -0
- package/src/combinator/effect/state.ts +72 -0
- package/src/combinator/{data/list → parser}/list.ts +35 -10
- package/src/combinator/parser.ts +303 -0
- package/src/combinator/process/bind.ts +34 -0
- package/src/combinator/process/block.test.ts +20 -0
- package/src/combinator/process/block.ts +33 -0
- package/src/combinator/process/clear.ts +16 -0
- package/src/combinator/process/contract.ts +35 -0
- package/src/combinator/process/duplicate.ts +7 -0
- package/src/combinator/process/error.ts +13 -0
- package/src/combinator/{control/manipulation → process}/fallback.ts +3 -3
- package/src/combinator/process/fence.ts +59 -0
- package/src/combinator/process/fmap.ts +10 -0
- package/src/combinator/process/indent.test.ts +31 -0
- package/src/combinator/process/indent.ts +51 -0
- package/src/combinator/process/lazy.ts +8 -0
- package/src/combinator/process/line.test.ts +21 -0
- package/src/combinator/process/line.ts +55 -0
- package/src/combinator/process/match.ts +37 -0
- package/src/combinator/process/reverse.ts +7 -0
- package/src/combinator/process/scope.ts +102 -0
- package/src/combinator/process/surround.ts +273 -0
- package/src/combinator.ts +28 -24
- package/src/debug.test.ts +11 -8
- package/src/parser/autolink.test.ts +17 -18
- package/src/parser/block/blockquote.test.ts +78 -79
- package/src/parser/block/blockquote.ts +32 -25
- package/src/parser/block/codeblock.test.ts +54 -57
- package/src/parser/block/codeblock.ts +44 -26
- package/src/parser/block/dlist.test.ts +56 -57
- package/src/parser/block/dlist.ts +5 -5
- package/src/parser/block/extension/aside.test.ts +7 -9
- package/src/parser/block/extension/aside.ts +76 -47
- package/src/parser/block/extension/example.test.ts +16 -19
- package/src/parser/block/extension/example.ts +88 -48
- package/src/parser/block/extension/fig.test.ts +37 -36
- package/src/parser/block/extension/fig.ts +20 -25
- package/src/parser/block/extension/figbase.test.ts +18 -19
- package/src/parser/block/extension/figbase.ts +3 -3
- package/src/parser/block/extension/figure.test.ts +58 -63
- package/src/parser/block/extension/figure.ts +23 -21
- package/src/parser/block/extension/message.test.ts +12 -14
- package/src/parser/block/extension/message.ts +52 -39
- package/src/parser/block/extension/placeholder.test.ts +13 -13
- package/src/parser/block/extension/placeholder.ts +23 -21
- package/src/parser/block/extension/table.test.ts +69 -71
- package/src/parser/block/extension/table.ts +43 -31
- package/src/parser/block/extension.test.ts +24 -24
- package/src/parser/block/extension.ts +3 -3
- package/src/parser/block/heading.test.ts +58 -59
- package/src/parser/block/heading.ts +19 -18
- package/src/parser/block/ilist.test.ts +8 -8
- package/src/parser/block/ilist.ts +9 -7
- package/src/parser/block/mathblock.test.ts +29 -32
- package/src/parser/block/mathblock.ts +24 -23
- package/src/parser/block/mediablock.ts +7 -7
- package/src/parser/block/olist.test.ts +102 -103
- package/src/parser/block/olist.ts +11 -12
- package/src/parser/block/pagebreak.test.ts +15 -16
- package/src/parser/block/pagebreak.ts +5 -5
- package/src/parser/block/paragraph.test.ts +57 -58
- package/src/parser/block/paragraph.ts +1 -1
- package/src/parser/block/reply/cite.test.ts +39 -40
- package/src/parser/block/reply/cite.ts +5 -5
- package/src/parser/block/reply/quote.test.ts +50 -51
- package/src/parser/block/reply/quote.ts +8 -7
- package/src/parser/block/reply.test.ts +19 -20
- package/src/parser/block/reply.ts +2 -2
- package/src/parser/block/sidefence.test.ts +41 -48
- package/src/parser/block/sidefence.ts +17 -11
- package/src/parser/block/table.test.ts +48 -49
- package/src/parser/block/table.ts +10 -9
- package/src/parser/block/ulist.test.ts +52 -53
- package/src/parser/block/ulist.ts +9 -8
- package/src/parser/block.ts +63 -51
- package/src/parser/context.ts +40 -40
- package/src/parser/document.ts +48 -0
- package/src/parser/header.test.ts +19 -20
- package/src/parser/header.ts +31 -25
- package/src/parser/inline/annotation.test.ts +49 -50
- package/src/parser/inline/annotation.ts +14 -16
- package/src/parser/inline/autolink/account.test.ts +32 -33
- package/src/parser/inline/autolink/account.ts +18 -19
- package/src/parser/inline/autolink/anchor.test.ts +21 -22
- package/src/parser/inline/autolink/anchor.ts +7 -8
- package/src/parser/inline/autolink/channel.test.ts +14 -15
- package/src/parser/inline/autolink/email.test.ts +36 -37
- package/src/parser/inline/autolink/email.ts +6 -6
- package/src/parser/inline/autolink/hashnum.test.ts +32 -33
- package/src/parser/inline/autolink/hashnum.ts +7 -8
- package/src/parser/inline/autolink/hashtag.test.ts +59 -60
- package/src/parser/inline/autolink/hashtag.ts +8 -9
- package/src/parser/inline/autolink/url.test.ts +75 -76
- package/src/parser/inline/autolink/url.ts +17 -18
- package/src/parser/inline/autolink.ts +24 -11
- package/src/parser/inline/bracket.test.ts +73 -74
- package/src/parser/inline/bracket.ts +88 -63
- package/src/parser/inline/code.test.ts +30 -31
- package/src/parser/inline/code.ts +6 -6
- package/src/parser/inline/deletion.test.ts +27 -28
- package/src/parser/inline/deletion.ts +5 -5
- package/src/parser/inline/emphasis.test.ts +39 -40
- package/src/parser/inline/emphasis.ts +5 -5
- package/src/parser/inline/emstrong.test.ts +101 -102
- package/src/parser/inline/emstrong.ts +103 -85
- package/src/parser/inline/extension/index.test.ts +91 -92
- package/src/parser/inline/extension/index.ts +17 -13
- package/src/parser/inline/extension/indexee.ts +4 -4
- package/src/parser/inline/extension/indexer.test.ts +23 -24
- package/src/parser/inline/extension/indexer.ts +6 -5
- package/src/parser/inline/extension/label.test.ts +32 -33
- package/src/parser/inline/extension/label.ts +14 -5
- package/src/parser/inline/extension/placeholder.test.ts +42 -43
- package/src/parser/inline/extension/placeholder.ts +8 -9
- package/src/parser/inline/html.test.ts +109 -109
- package/src/parser/inline/html.ts +27 -27
- package/src/parser/inline/htmlentity.test.ts +37 -38
- package/src/parser/inline/htmlentity.ts +6 -7
- package/src/parser/inline/insertion.test.ts +27 -28
- package/src/parser/inline/insertion.ts +5 -5
- package/src/parser/inline/italic.test.ts +55 -56
- package/src/parser/inline/italic.ts +5 -5
- package/src/parser/inline/link.test.ts +186 -187
- package/src/parser/inline/link.ts +29 -30
- package/src/parser/inline/mark.test.ts +31 -32
- package/src/parser/inline/mark.ts +6 -6
- package/src/parser/inline/math.test.ts +140 -141
- package/src/parser/inline/math.ts +7 -8
- package/src/parser/inline/media.test.ts +92 -93
- package/src/parser/inline/media.ts +35 -41
- package/src/parser/inline/reference.test.ts +111 -112
- package/src/parser/inline/reference.ts +61 -32
- package/src/parser/inline/remark.test.ts +49 -50
- package/src/parser/inline/remark.ts +13 -13
- package/src/parser/inline/ruby.test.ts +49 -50
- package/src/parser/inline/ruby.ts +100 -52
- package/src/parser/inline/shortmedia.test.ts +9 -10
- package/src/parser/inline/shortmedia.ts +11 -9
- package/src/parser/inline/strong.test.ts +36 -37
- package/src/parser/inline/strong.ts +5 -5
- package/src/parser/inline/template.test.ts +22 -23
- package/src/parser/inline/template.ts +17 -20
- package/src/parser/inline.test.ts +225 -226
- package/src/parser/inline.ts +68 -34
- package/src/parser/parser.ts +51 -0
- package/src/parser/repeat.ts +118 -91
- package/src/parser/segment.test.ts +0 -11
- package/src/parser/segment.ts +25 -28
- package/src/parser/source/escapable.test.ts +23 -24
- package/src/parser/source/escapable.ts +19 -20
- package/src/parser/source/line.test.ts +17 -18
- package/src/parser/source/line.ts +19 -24
- package/src/parser/source/str.ts +17 -10
- package/src/parser/source/text.test.ts +88 -89
- package/src/parser/source/text.ts +32 -62
- package/src/parser/source/unescapable.test.ts +23 -24
- package/src/parser/source/unescapable.ts +15 -16
- package/src/parser/source/whitespace.ts +36 -0
- package/src/parser/source.ts +1 -0
- package/src/parser/util.ts +1 -1
- package/src/parser/visibility.ts +37 -15
- package/src/processor/figure.test.ts +20 -20
- package/src/processor/figure.ts +18 -10
- package/src/processor/note.test.ts +13 -13
- package/src/processor/note.ts +4 -2
- package/src/renderer/render/media/pdf.ts +2 -2
- package/src/renderer/render/media/twitter.ts +2 -2
- package/src/renderer/render/media.test.ts +12 -12
- package/src/renderer/render.test.ts +11 -11
- package/src/util/info.test.ts +2 -2
- package/src/util/quote.test.ts +3 -3
- package/src/util/quote.ts +6 -5
- package/src/util/toc.test.ts +12 -12
- package/src/combinator/control/constraint/block.test.ts +0 -20
- package/src/combinator/control/constraint/block.ts +0 -28
- package/src/combinator/control/constraint/contract.ts +0 -27
- package/src/combinator/control/constraint/line.test.ts +0 -21
- package/src/combinator/control/constraint/line.ts +0 -42
- package/src/combinator/control/manipulation/clear.ts +0 -5
- package/src/combinator/control/manipulation/convert.ts +0 -22
- package/src/combinator/control/manipulation/duplicate.ts +0 -7
- package/src/combinator/control/manipulation/fence.ts +0 -54
- package/src/combinator/control/manipulation/indent.test.ts +0 -31
- package/src/combinator/control/manipulation/indent.ts +0 -39
- package/src/combinator/control/manipulation/lazy.ts +0 -8
- package/src/combinator/control/manipulation/match.ts +0 -27
- package/src/combinator/control/manipulation/recovery.ts +0 -18
- package/src/combinator/control/manipulation/reverse.ts +0 -8
- package/src/combinator/control/manipulation/scope.ts +0 -61
- package/src/combinator/control/manipulation/surround.ts +0 -223
- package/src/combinator/control/monad/bind.ts +0 -26
- package/src/combinator/control/monad/fmap.ts +0 -10
- package/src/combinator/data/parser/context.ts +0 -96
- package/src/combinator/data/parser/inits.ts +0 -20
- package/src/combinator/data/parser/sequence.test.ts +0 -33
- package/src/combinator/data/parser/sequence.ts +0 -20
- package/src/combinator/data/parser/some.test.ts +0 -37
- package/src/combinator/data/parser/subsequence.test.ts +0 -41
- package/src/combinator/data/parser/subsequence.ts +0 -13
- package/src/combinator/data/parser/tails.ts +0 -8
- package/src/combinator/data/parser/union.test.ts +0 -33
- package/src/combinator/data/parser/union.ts +0 -18
- package/src/combinator/data/parser.ts +0 -144
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Delimiters } from './delimiter';
|
|
2
|
+
import { Backtrack } from './effect/backtrack';
|
|
3
|
+
import { Scope } from './effect/scope';
|
|
4
|
+
import { List, Node } from './parser/list';
|
|
5
|
+
|
|
6
|
+
// パーサーは基本的に成否の文脈に関わらず実行され成否による処理の分岐は可能な限り
|
|
7
|
+
// 文脈の検査でなく継続の分岐により行わなければならない。
|
|
8
|
+
export type Parser<T = unknown, I extends Input = Input, S extends SubParsers<unknown, I> = SubParsers<never, I>> =
|
|
9
|
+
(input: I, output: Output<T>) => Result<T, I, S>;
|
|
10
|
+
export type SubParsers<T, I extends Input = Input> = readonly Parser<T, I, SubParsers<never, I>>[];
|
|
11
|
+
export namespace Parser {
|
|
12
|
+
export type Node<P extends Parser> = P extends Parser<infer T> ? T : never;
|
|
13
|
+
export type Input<P extends Parser> = P extends Parser<unknown, infer I> ? I : never;
|
|
14
|
+
export type SubParsers<P extends Parser> = P extends Parser<unknown, any, infer S> ? S : never;
|
|
15
|
+
export type IntermediateParser<P extends Parser> = Parser<SubNode<P>, Input<P>, SubParsers<P>>;
|
|
16
|
+
export type SubNode<P extends Parser> = NonNever<ExtractSubNode<SubParsers<P>>, Node<P>>;
|
|
17
|
+
type NonNever<T, U> = [T] extends [never] ? U : T;
|
|
18
|
+
type ExtractSubNode<S extends readonly Parser[]> = ExtractSubParser<S> extends infer P ? P extends Parser<infer T> ? T : never : never;
|
|
19
|
+
type ExtractSubParser<S extends readonly Parser[]> = S extends readonly (infer P)[] ? P : never;
|
|
20
|
+
}
|
|
21
|
+
export type Result<T, I extends Input = Input, S extends SubParsers<unknown, I> = SubParsers<never, I>> =
|
|
22
|
+
| Result.Cont<T, I, S>
|
|
23
|
+
| Result.Fail
|
|
24
|
+
| Result.Skip;
|
|
25
|
+
export namespace Result {
|
|
26
|
+
export type Cont<T, I extends Input = Input, S extends SubParsers<unknown, I> = SubParsers<never, I>> =
|
|
27
|
+
readonly Parser<T, I, S>[];
|
|
28
|
+
export type Succ = readonly [];
|
|
29
|
+
export type Fail = undefined;
|
|
30
|
+
export type Skip = null;
|
|
31
|
+
export const succ = [] as const;
|
|
32
|
+
export const fail = undefined;
|
|
33
|
+
export const skip = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let SID = 0;
|
|
37
|
+
function sid(): number {
|
|
38
|
+
return SID = ++SID >>> 0 || 1;
|
|
39
|
+
}
|
|
40
|
+
export class Input<M extends object = object> {
|
|
41
|
+
constructor(
|
|
42
|
+
{
|
|
43
|
+
source,
|
|
44
|
+
position,
|
|
45
|
+
resources,
|
|
46
|
+
linebreak,
|
|
47
|
+
range,
|
|
48
|
+
offset,
|
|
49
|
+
delimiters,
|
|
50
|
+
backtracks,
|
|
51
|
+
scope,
|
|
52
|
+
backtrack,
|
|
53
|
+
precedence,
|
|
54
|
+
state,
|
|
55
|
+
memory,
|
|
56
|
+
}: Options = {},
|
|
57
|
+
) {
|
|
58
|
+
this.source = source ?? '';
|
|
59
|
+
this.position = position ?? 0;
|
|
60
|
+
this.resources = resources;
|
|
61
|
+
this.linebreak = linebreak ?? 0;
|
|
62
|
+
this.range = range ?? 0;
|
|
63
|
+
this.offset = offset ?? 0;
|
|
64
|
+
this.delimiters = delimiters ?? new Delimiters();
|
|
65
|
+
this.backtracks = backtracks ?? {};
|
|
66
|
+
this.scope = scope as Scope<this> ?? new Scope(this);
|
|
67
|
+
this.backtrack = backtrack ?? new Backtrack(this.scope);
|
|
68
|
+
this.precedence = precedence ?? 0;
|
|
69
|
+
this.state = state ?? 0;
|
|
70
|
+
this.memory = memory ?? Queue.none;
|
|
71
|
+
}
|
|
72
|
+
public SID: number = sid();
|
|
73
|
+
public source: string;
|
|
74
|
+
public segment: number = Segment.unknown;
|
|
75
|
+
public position: number;
|
|
76
|
+
public readonly resources?: {
|
|
77
|
+
clock: number;
|
|
78
|
+
recursions: number[];
|
|
79
|
+
interval?: number;
|
|
80
|
+
};
|
|
81
|
+
public linebreak: number;
|
|
82
|
+
public range: number;
|
|
83
|
+
public offset: number;
|
|
84
|
+
public delimiters: Delimiters;
|
|
85
|
+
// Objectの内部実装を利用する。
|
|
86
|
+
// 探索木を直接使用する場合は探索速度が重要で挿入は相対的に少なく削除は不要かつ不確実であるため
|
|
87
|
+
// AVL木が適当と思われる。
|
|
88
|
+
// メモリの局所性を得るために木ごとに最初の数十から数百byte分のノードをプールしノードが不足した場合は
|
|
89
|
+
// 使い捨てノードを追加またはテーブルに移行するとよいだろう。
|
|
90
|
+
// 最大セグメントサイズ10KB内で探索コストが平均実行性能を圧迫するほど大きくなるとは考えにくいが
|
|
91
|
+
// 探索コストを減らすにはバックトラック位置数が規定数を超えた場合一定区間ごとに探索木を分割する方法が考えられる。
|
|
92
|
+
// 10KBの入力すべてを保持する探索木を1024文字ごとに分割するために必要なテーブルサイズは64bit*98=784byteとなる。
|
|
93
|
+
// 128文字ごとでもテーブルサイズは入力全体の1%未満であるため無視でき16文字ごとでさえ6.25%に過ぎない。
|
|
94
|
+
// 探索木のポインタによるオーバーヘッドを考慮すれば一定サイズ以上ではテーブルのほうが効率的となる。
|
|
95
|
+
// 区間別テーブルは固定サイズであるためプールして再使用できる。
|
|
96
|
+
// 従って分割時のデータ構造は区間ごとに探索木を動的に生成しデータ数に応じてテーブルに移行するのが最も効率的である。
|
|
97
|
+
// これにより最悪時間計算量線形化に要する最悪空間計算量が+1nに局限される。
|
|
98
|
+
// またはテーブルの参照が高速なら変換せず併用してもよい。
|
|
99
|
+
// 木とテーブルいずれにおいてもバックトラックデータとオーバーヘッドを合わせた追加データサイズの最大値は
|
|
100
|
+
// セグメントサイズに制約されるため入力サイズに対する最大追加データサイズの平均比率はかなり小さくなる。
|
|
101
|
+
// 必要なテーブルの最大サイズは最大セグメントサイズであるため最大追加データサイズは入力サイズにかかわらず
|
|
102
|
+
// 10KB*並列数に留まり最大数百文字以下の短文ならば数百byte*並列数となる。
|
|
103
|
+
//
|
|
104
|
+
// 1. データ数が規定数を超えたら区間テーブルを生成しデータを振り分ける。
|
|
105
|
+
// - 子ノードのポインタだけ保持するとしても1ノード複数データ保持で圧縮できるかは微妙。
|
|
106
|
+
// - 1ノードに2データ保持すれば2連続データを1/2の確率で捕捉し1バックトラックあたりの平均追加データサイズは
|
|
107
|
+
// -7byte(((16+1)*2-(16+2))*2+((16+1)*2-(16+2)*2)*2)/4=(32-4)/4=7の10byteに減少する。
|
|
108
|
+
// 2連続データの発生確率が1/5なら-3.2byteの13.8byte、1/10なら+0.4byteの17.4byteに増加する。
|
|
109
|
+
// - 1ノードに4データ保持すれば2連続データを3/4の確率で捕捉し1バックトラックあたりの平均追加データサイズは
|
|
110
|
+
// -9byte(((16+1)*2-(16+4))*3+((16+1)*2-(16+4)*2))/4=(42-6)/4=9の8byteに減少する。
|
|
111
|
+
// 2連続データの発生確率が1/5なら-3.6byteの13.4byte、1/10なら+1.2byteの18.2byteに増加する。
|
|
112
|
+
// 2. 区間内のデータ構造は探索木から開始しデータ数が規定数を超えたらテーブルに変換する。
|
|
113
|
+
// - 1ノード1データ1区間1024文字ならば1024<(64/8*2+1)*61から1区間61データ以上でテーブルのほうが小さくなる。
|
|
114
|
+
// - 64/8*2+1=17文字に1か所以下のバックトラックでテーブル以上の効率となる。
|
|
115
|
+
// - 通常の入力でバックトラックが17文字に平均1か所以上となることは考えられず
|
|
116
|
+
// 1段落数百文字あたり平均2、3か所以下が妥当な頻度でありこの場合の最大追加データサイズは
|
|
117
|
+
// 入力内の最大セグメントサイズの10%前後である。
|
|
118
|
+
//
|
|
119
|
+
public backtracks: Record<number, number>;
|
|
120
|
+
public readonly scope: Scope<this>;
|
|
121
|
+
public readonly backtrack: Backtrack;
|
|
122
|
+
public precedence: number;
|
|
123
|
+
public state: number;
|
|
124
|
+
// 必ず自分の継続ブロックの中で使い汚染を防ぐ
|
|
125
|
+
// 本来はスタックのほうが使い捨てオブジェクトを作らず効率的だが全体としては誤差か
|
|
126
|
+
public memory: M;
|
|
127
|
+
public clone(source: string): this {
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
const i = input(source, new this.constructor(this)) as this;
|
|
130
|
+
i.offset = 0;
|
|
131
|
+
i.backtracks = {};
|
|
132
|
+
return i;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export type Options = Partial<Input>;
|
|
136
|
+
export class Output<T> {
|
|
137
|
+
constructor(
|
|
138
|
+
public readonly data: [List<Node<T>>, ...List<Node<T>>[]] = [new List()],
|
|
139
|
+
) {
|
|
140
|
+
assert(data.length > 0);
|
|
141
|
+
}
|
|
142
|
+
public state: boolean = true;
|
|
143
|
+
public readonly context: Result.Succ | Result.Fail = Result.succ;
|
|
144
|
+
public error?: Error = undefined;
|
|
145
|
+
public peek(): List<Node<T>> {
|
|
146
|
+
assert(this.data.length > 0);
|
|
147
|
+
return this.data.at(-1)!;
|
|
148
|
+
}
|
|
149
|
+
public prepend(node: Node<T>): Result.Succ | Result.Fail {
|
|
150
|
+
this.data.at(-1)!.unshift(node);
|
|
151
|
+
return Result.succ;
|
|
152
|
+
}
|
|
153
|
+
public append(node: Node<T>): Result.Succ | Result.Fail {
|
|
154
|
+
this.data.at(-1)!.push(node);
|
|
155
|
+
return Result.succ;
|
|
156
|
+
}
|
|
157
|
+
public import(list: List<Node<T>>): Result.Succ | Result.Fail {
|
|
158
|
+
this.data.at(-1)!.import(list);
|
|
159
|
+
return Result.succ;
|
|
160
|
+
}
|
|
161
|
+
public replace(list: List<Node<T>>): Result.Succ | Result.Fail {
|
|
162
|
+
assert(this.data.length > 1);
|
|
163
|
+
this.data[this.data.length - 1] = list;
|
|
164
|
+
return Result.succ;
|
|
165
|
+
}
|
|
166
|
+
public flat(): Result.Succ | Result.Fail {
|
|
167
|
+
assert(this.data.length > 1);
|
|
168
|
+
this.import(this.data.pop()!);
|
|
169
|
+
return Result.succ;
|
|
170
|
+
}
|
|
171
|
+
public push(list: List<Node<T>> = new List()): void {
|
|
172
|
+
this.data.push(list);
|
|
173
|
+
}
|
|
174
|
+
public pop(): List<Node<T>> {
|
|
175
|
+
//assert(this.data.length > +!force);
|
|
176
|
+
return this.data.pop()!;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export const enum Segment {
|
|
180
|
+
unknown = 0,
|
|
181
|
+
read = 0,
|
|
182
|
+
write = 1,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { List, Node };
|
|
186
|
+
|
|
187
|
+
export function input<I extends Input>(source: string, input: I): I;
|
|
188
|
+
export function input(source: string, input?: Input): Input;
|
|
189
|
+
export function input(source: string, input: Input = new Input()): Input {
|
|
190
|
+
input = subinput(source, input);
|
|
191
|
+
input.segment &= Segment.write;
|
|
192
|
+
return input;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function subinput<I extends Input>(source: string, input: I): I;
|
|
196
|
+
export function subinput(source: string, input?: Input): Input;
|
|
197
|
+
export function subinput(source: string, input: Input = new Input()): Input {
|
|
198
|
+
input.SID = sid();
|
|
199
|
+
input.source = source;
|
|
200
|
+
input.offset += input.position;
|
|
201
|
+
input.position = 0;
|
|
202
|
+
input.linebreak = 0;
|
|
203
|
+
input.range = 0;
|
|
204
|
+
return input;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 全継続を末尾再帰にすればスタック不要になるがパーサーを複合すると後処理の予約のスタックを
|
|
208
|
+
// 避けられないため不可能。
|
|
209
|
+
export function* run
|
|
210
|
+
<T, I extends Input, O extends Output<T>>
|
|
211
|
+
(parser: Parser<T, I>, input: I, output: O)
|
|
212
|
+
: Generator<void, void, void> {
|
|
213
|
+
assert(output.data.length > 0);
|
|
214
|
+
const { scope, resources = { clock: 0, interval: 0 } } = input;
|
|
215
|
+
const { interval = 0 } = resources;
|
|
216
|
+
const stack: Queue<Parser<T, I>>[] = [];
|
|
217
|
+
let index = stack.length;
|
|
218
|
+
let time = interval && Date.now();
|
|
219
|
+
for (let queue = Queue.from([parser]); ;) {
|
|
220
|
+
if (interval && resources.clock << 32 - 16 === 0 && Date.now() - time > interval) {
|
|
221
|
+
yield;
|
|
222
|
+
time = Date.now();
|
|
223
|
+
}
|
|
224
|
+
if (output.state && output.error) {
|
|
225
|
+
if (output.state) {
|
|
226
|
+
output.state = false;
|
|
227
|
+
// @ts-expect-error
|
|
228
|
+
output.context = Result.fail;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const input = scope.peek();
|
|
232
|
+
//assert(input.position <= input.source.length);
|
|
233
|
+
const parser = queue.pop();
|
|
234
|
+
const result = parser(input, output);
|
|
235
|
+
|
|
236
|
+
if (result) {
|
|
237
|
+
//assert(result.every(f => f));
|
|
238
|
+
if (!output.state) {
|
|
239
|
+
output.state = true;
|
|
240
|
+
// @ts-expect-error
|
|
241
|
+
output.context ??= Result.succ;
|
|
242
|
+
}
|
|
243
|
+
if (result.length !== 0) {
|
|
244
|
+
if (queue.length !== 0) {
|
|
245
|
+
queue.memory = input.memory;
|
|
246
|
+
stack[index++] = queue;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
Queue.dispose(queue);
|
|
250
|
+
}
|
|
251
|
+
queue = Queue.from(result);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
if (result === Result.skip) {
|
|
257
|
+
queue.length = 0;
|
|
258
|
+
}
|
|
259
|
+
if (output.state) {
|
|
260
|
+
output.state = false;
|
|
261
|
+
// @ts-expect-error
|
|
262
|
+
output.context = Result.fail;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (queue.length !== 0) continue;
|
|
267
|
+
Queue.dispose(queue);
|
|
268
|
+
if (index === 0) break;
|
|
269
|
+
queue = stack[--index];
|
|
270
|
+
scope.peek().memory = queue.memory;
|
|
271
|
+
//stack.length - index === 256 && stack.pop();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
class Queue<T> {
|
|
276
|
+
private static readonly pool: Queue<any>[] = Array(256).fill(null);
|
|
277
|
+
private static index = 0;
|
|
278
|
+
public static from<T>(items: readonly T[]): Queue<T> {
|
|
279
|
+
if (this.index === 0) return new Queue(items);
|
|
280
|
+
const queue = this.pool[--this.index];
|
|
281
|
+
queue.items = items;
|
|
282
|
+
queue.length = items.length;
|
|
283
|
+
return queue;
|
|
284
|
+
}
|
|
285
|
+
public static dispose(queue: Queue<any>): void {
|
|
286
|
+
queue.items = Result.succ;
|
|
287
|
+
queue.memory = this.none;
|
|
288
|
+
if (this.index === this.pool.length) return;
|
|
289
|
+
this.pool[this.index++] = queue;
|
|
290
|
+
}
|
|
291
|
+
private constructor(
|
|
292
|
+
public items: readonly T[],
|
|
293
|
+
) {
|
|
294
|
+
}
|
|
295
|
+
public static readonly none = Object.freeze(Object.create(null));
|
|
296
|
+
public memory: object = Queue.none;
|
|
297
|
+
public length = this.items.length;
|
|
298
|
+
public pop(): T {
|
|
299
|
+
//assert(this.length > 0);
|
|
300
|
+
const { items } = this;
|
|
301
|
+
return items[items.length - this.length--];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Parser, Result, Input, Output, List, Node } from '../parser';
|
|
2
|
+
import { always } from '../control/state';
|
|
3
|
+
|
|
4
|
+
type ResultM<T> = List<Node<T>> | undefined;
|
|
5
|
+
|
|
6
|
+
export function bind<P extends Parser>(parser: Parser.IntermediateParser<P>, f: (nodes: List<Node<Parser.SubNode<P>>>, Input: Parser.Input<P>, output: Output<Parser.Node<P>>) => ResultM<Parser.Node<P>>): P;
|
|
7
|
+
export function bind<P extends Parser>(parser: P, f: (nodes: List<Node<Parser.Node<P>>>, Input: Parser.Input<P>, output: Output<Parser.Node<P>>) => ResultM<Parser.Node<P>>): P;
|
|
8
|
+
export function bind<T, P extends Parser>(parser: Parser<T, Parser.Input<P>, Parser.SubParsers<P>>, f: (nodes: List<Node<T>>, Input: Parser.Input<P>, output: Output<Parser.Node<P>>) => ResultM<Parser.Node<P>>): P;
|
|
9
|
+
export function bind<U, P extends Parser>(parser: P, f: (nodes: List<Node<Parser.Node<P>>>, Input: Parser.Input<P>, output: Output<Parser.Node<P>>) => ResultM<U>): Parser<U, Parser.Input<P>, Parser.SubParsers<P>>;
|
|
10
|
+
export function bind<T>(parser: Parser<T>, f: (nodes: List<Node<T>>, Input: Input, output: Output<T>) => ResultM<T>): Parser<T> {
|
|
11
|
+
assert(parser);
|
|
12
|
+
interface Memory {
|
|
13
|
+
readonly position: number;
|
|
14
|
+
}
|
|
15
|
+
return always<Parser<T, Input<Memory>>>([
|
|
16
|
+
(input, output) => {
|
|
17
|
+
input.memory = {
|
|
18
|
+
position: input.position,
|
|
19
|
+
};
|
|
20
|
+
output.push();
|
|
21
|
+
return output.context;
|
|
22
|
+
},
|
|
23
|
+
parser,
|
|
24
|
+
(input, output) => {
|
|
25
|
+
const nodes = output.pop();
|
|
26
|
+
if (!output.state) return;
|
|
27
|
+
input.range = input.position - input.memory.position;
|
|
28
|
+
const result = f(nodes, input, output);
|
|
29
|
+
return result
|
|
30
|
+
? output.import(result)
|
|
31
|
+
: Result.fail;
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { block } from './block';
|
|
2
|
+
import { input } from '../parser';
|
|
3
|
+
import { inspect } from '../../debug.test';
|
|
4
|
+
|
|
5
|
+
describe('Unit: lib/parser/combinator/block', () => {
|
|
6
|
+
describe('block', () => {
|
|
7
|
+
it('invalid', () => {
|
|
8
|
+
assert.throws(() => inspect(block(() => []), (input(' \n'))));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('valid', () => {
|
|
12
|
+
assert.deepStrictEqual(inspect(block(input => { input.position = input.source.length; return []; }), input('\n')), [[], '']);
|
|
13
|
+
assert.deepStrictEqual(inspect(block(input => { input.position = input.source.length; return []; }), input(' \n')), [[], '']);
|
|
14
|
+
assert.deepStrictEqual(inspect(block(input => { input.position = input.source.length; return []; }), input('\n\n')), [[], '']);
|
|
15
|
+
assert.deepStrictEqual(inspect(block(input => { input.position = input.source.length - 1; return []; }), input('\n\n')), [[], '\n']);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Parser, Result, Segment } from '../parser';
|
|
2
|
+
import { always } from '../control/state';
|
|
3
|
+
import { backtrack } from '../effect/backtrack';
|
|
4
|
+
import { isEmptyline } from './line';
|
|
5
|
+
|
|
6
|
+
export function block<P extends Parser>(parser: P, separation?: boolean, segment?: number): P;
|
|
7
|
+
export function block<T>(parser: Parser<T>, separation = true, segment = 0): Parser<T> {
|
|
8
|
+
assert(parser);
|
|
9
|
+
const cont: Result<T> = [
|
|
10
|
+
parser,
|
|
11
|
+
(input, output) => {
|
|
12
|
+
const { source, position } = input;
|
|
13
|
+
if (!output.state || separation && !isEmptyline(source, position)) return;
|
|
14
|
+
assert(position === source.length || source[position - 1] === '\n');
|
|
15
|
+
if (segment !== 0 && ~input.segment & Segment.write) {
|
|
16
|
+
input.segment = segment;
|
|
17
|
+
}
|
|
18
|
+
return output.context;
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
return backtrack(always([
|
|
22
|
+
(input, output) => {
|
|
23
|
+
const { source, position } = input;
|
|
24
|
+
if (position === source.length) return;
|
|
25
|
+
if (segment !== 0 && input.segment & Segment.write) {
|
|
26
|
+
if (input.segment !== (segment | Segment.write)) return;
|
|
27
|
+
input.position = source.length;
|
|
28
|
+
return output.context;
|
|
29
|
+
}
|
|
30
|
+
return cont;
|
|
31
|
+
},
|
|
32
|
+
]));
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Parser, SubParsers, Input } from '../parser';
|
|
2
|
+
import { always } from '../control/state';
|
|
3
|
+
|
|
4
|
+
export function clear<S extends SubParsers<unknown>, I extends Input>(parser: Parser<unknown, I, S>): Parser<never, I, S> {
|
|
5
|
+
return always([
|
|
6
|
+
(_, output) => {
|
|
7
|
+
output.push();
|
|
8
|
+
return output.context;
|
|
9
|
+
},
|
|
10
|
+
parser,
|
|
11
|
+
(_, output) => {
|
|
12
|
+
output.pop();
|
|
13
|
+
return output.context;
|
|
14
|
+
},
|
|
15
|
+
]);
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Parser, Result, Input, Output } from '../parser';
|
|
2
|
+
import { always } from '../control/state';
|
|
3
|
+
import { tester } from '../delimiter';
|
|
4
|
+
|
|
5
|
+
export function contract<P extends Parser>(patterns: string | RegExp, parser: P, cond: (input: Parser.Input<P>, output: Output<Parser.Node<P>>) => boolean): P;
|
|
6
|
+
export function contract<P extends Parser>(precond: (input: Parser.Input<P>, output: Output<Parser.Node<P>>) => boolean, parser: P, postcond: (input: Parser.Input<P>, output: Output<Parser.Node<P>>) => boolean): P;
|
|
7
|
+
export function contract<T>(precond: string | RegExp | ((input: Input, output: Output<T>) => boolean), parser: Parser<T>, postcond: (input: Input, output: Output<T>) => boolean): Parser<T> {
|
|
8
|
+
if (typeof precond !== 'function') {
|
|
9
|
+
precond = tester(precond, false);
|
|
10
|
+
}
|
|
11
|
+
return validate(precond, verify(parser, postcond));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function validate<P extends Parser>(pattern: string | RegExp, parser: P): P;
|
|
15
|
+
export function validate<P extends Parser>(cond: (input: Parser.Input<P>, output: Output<Parser.Node<P>>) => boolean, parser: P): P;
|
|
16
|
+
export function validate<T>(cond: string | RegExp | ((input: Input, output: Output<T>) => boolean), parser: Parser<T>): Parser<T> {
|
|
17
|
+
if (typeof cond !== 'function') {
|
|
18
|
+
cond = tester(cond, false);
|
|
19
|
+
}
|
|
20
|
+
return always([
|
|
21
|
+
(input, output) =>
|
|
22
|
+
output.state && cond(input, output) ? output.context : Result.skip,
|
|
23
|
+
parser,
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function verify<P extends Parser>(parser: P, cond: (input: Parser.Input<P>, output: Output<Parser.Node<P>>) => boolean): P;
|
|
28
|
+
export function verify<T>(parser: Parser<T>, cond: (input: Input, output: Output<T>) => boolean): Parser<T> {
|
|
29
|
+
assert(parser);
|
|
30
|
+
return always([
|
|
31
|
+
parser,
|
|
32
|
+
(input, output) =>
|
|
33
|
+
output.state && cond(input, output) ? output.context : Result.fail,
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Parser, SubParsers, Input, List, Node } from '../parser';
|
|
2
|
+
import { fmap } from './fmap';
|
|
3
|
+
|
|
4
|
+
export function dup<T, I extends Input, S extends SubParsers<T, I>>(parser: Parser<T, I, S>): Parser<List<Node<T>>, I, S>;
|
|
5
|
+
export function dup<T>(parser: Parser<T>): Parser<List<Node<T>>> {
|
|
6
|
+
return fmap(parser, nodes => new List([new Node(nodes)]));
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Parser } from '../parser';
|
|
2
|
+
import { always } from '../control/state';
|
|
3
|
+
|
|
4
|
+
export function error<P extends Parser>(parser: P): P;
|
|
5
|
+
export function error<T>(parser: Parser<T>): Parser<T> {
|
|
6
|
+
assert(parser);
|
|
7
|
+
return always([
|
|
8
|
+
(input, output) => {
|
|
9
|
+
if (!output.error) return output.context;
|
|
10
|
+
return parser(input, output);
|
|
11
|
+
},
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Parser } from '
|
|
2
|
-
import { union } from '
|
|
1
|
+
import { Parser, SubParsers } from '../parser';
|
|
2
|
+
import { union } from '../control/union';
|
|
3
3
|
|
|
4
|
-
export function fallback<P extends Parser>(parser: P, otherwise: Parser<Parser.Node<P>, Parser.
|
|
4
|
+
export function fallback<P extends Parser>(parser: P, otherwise: Parser<Parser.Node<P>, Parser.Input<P>, SubParsers<Parser.SubNode<P>, Parser.Input<P>>>): P;
|
|
5
5
|
export function fallback<N>(parser: Parser<N>, otherwise: Parser<N>): Parser<N> {
|
|
6
6
|
return union([parser, otherwise]);
|
|
7
7
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Parser, SubParsers, Input, List, Node } from '../parser';
|
|
2
|
+
import { spend } from '../effect/clock';
|
|
3
|
+
import { firstline, isEmptyline } from './line';
|
|
4
|
+
|
|
5
|
+
export function fence<I extends Input, S extends SubParsers<never, I>>(opener: RegExp, write: boolean, separation = true): Parser<string, I, S> {
|
|
6
|
+
assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^'));
|
|
7
|
+
return (input, output) => {
|
|
8
|
+
const { source, position } = input;
|
|
9
|
+
if (position === source.length) return;
|
|
10
|
+
opener.lastIndex = position;
|
|
11
|
+
const matches = opener.exec(source);
|
|
12
|
+
if (!matches) return;
|
|
13
|
+
assert(matches[0] === firstline(source, position));
|
|
14
|
+
spend(input, output, matches[0].length);
|
|
15
|
+
const delim = matches[1];
|
|
16
|
+
assert(delim && delim === delim.trim());
|
|
17
|
+
if (matches[0].includes(delim, delim.length)) return;
|
|
18
|
+
input.position += matches[0].length;
|
|
19
|
+
// Prevent annoying parsing in editing.
|
|
20
|
+
const secondline = firstline(source, input.position);
|
|
21
|
+
if (isEmptyline(secondline, 0) &&
|
|
22
|
+
firstline(source, input.position + secondline.length).trimEnd() !== delim) {
|
|
23
|
+
input.position -= matches[0].length;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
let body = '';
|
|
27
|
+
let closer = '';
|
|
28
|
+
let overflow = '';
|
|
29
|
+
for (let count = 1; ; ++count) {
|
|
30
|
+
if (input.position === source.length) break;
|
|
31
|
+
const line = firstline(source, input.position);
|
|
32
|
+
if (closer && isEmptyline(line, 0)) break;
|
|
33
|
+
if(closer) {
|
|
34
|
+
overflow += line;
|
|
35
|
+
}
|
|
36
|
+
if (!closer && line.startsWith(delim) && line.trimEnd() === delim) {
|
|
37
|
+
closer = line;
|
|
38
|
+
if (isEmptyline(source, input.position + line.length)) {
|
|
39
|
+
input.position += line.length;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (!separation) {
|
|
43
|
+
input.position += line.length;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
assert(!overflow);
|
|
47
|
+
overflow = line;
|
|
48
|
+
}
|
|
49
|
+
if (!overflow) {
|
|
50
|
+
body += line;
|
|
51
|
+
}
|
|
52
|
+
input.position += line.length;
|
|
53
|
+
}
|
|
54
|
+
write && output.push(
|
|
55
|
+
new List([body, overflow, closer].map(str => new Node(str)))
|
|
56
|
+
.import(new List(matches.map(str => new Node(str)))));
|
|
57
|
+
return output.context;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Parser, Input, Output, List, Node } from '../parser';
|
|
2
|
+
import { bind } from './bind';
|
|
3
|
+
|
|
4
|
+
export function fmap<P extends Parser>(parser: Parser.IntermediateParser<P>, f: (nodes: List<Node<Parser.SubNode<P>>>, input: Parser.Input<P>, output: Output<Parser.Node<P>>) => List<Node<Parser.Node<P>>>): P;
|
|
5
|
+
export function fmap<P extends Parser>(parser: P, f: (nodes: List<Node<Parser.Node<P>>>, input: Parser.Input<P>, output: Output<Parser.Node<P>>) => List<Node<Parser.Node<P>>>): P;
|
|
6
|
+
export function fmap<T, P extends Parser>(parser: Parser<T, Parser.Input<P>, Parser.SubParsers<P>>, f: (nodes: List<Node<T>>, input: Parser.Input<P>, output: Output<Parser.Node<P>>) => List<Node<Parser.Node<P>>>): P;
|
|
7
|
+
export function fmap<U, P extends Parser>(parser: P, f: (nodes: List<Node<Parser.Node<P>>>, input: Parser.Input<P>, output: Output<Parser.Node<P>>) => List<Node<U>>): Parser<U, Parser.Input<P>, Parser.SubParsers<P>>;
|
|
8
|
+
export function fmap<T>(parser: Parser<T>, f: (nodes: List<Node<T>>, input: Input, output: Output<T>) => List<Node<T>>): Parser<T> {
|
|
9
|
+
return bind(parser, (nodes, input, output) => f(nodes, input, output));
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { indent } from './indent';
|
|
2
|
+
import { Node, input } from '../parser';
|
|
3
|
+
import { inspect } from '../../debug.test';
|
|
4
|
+
|
|
5
|
+
describe('Unit: lib/parser/combinator/indent', () => {
|
|
6
|
+
describe('indent', () => {
|
|
7
|
+
it('valid', () => {
|
|
8
|
+
const parser = indent((input, output) => output.append(new Node(input.source)) && output.context);
|
|
9
|
+
assert.deepStrictEqual(inspect(parser, input('')), undefined);
|
|
10
|
+
assert.deepStrictEqual(inspect(parser, input(' ')), undefined);
|
|
11
|
+
assert.deepStrictEqual(inspect(parser, input(' ')), undefined);
|
|
12
|
+
assert.deepStrictEqual(inspect(parser, input('a ')), undefined);
|
|
13
|
+
assert.deepStrictEqual(inspect(parser, input(' a')), [['a'], '']);
|
|
14
|
+
assert.deepStrictEqual(inspect(parser, input(' a ')), [['a '], '']);
|
|
15
|
+
assert.deepStrictEqual(inspect(parser, input(' a\n')), [['a'], '']);
|
|
16
|
+
assert.deepStrictEqual(inspect(parser, input(' a \n')), [['a '], '']);
|
|
17
|
+
assert.deepStrictEqual(inspect(parser, input(' a')), [['a'], '']);
|
|
18
|
+
assert.deepStrictEqual(inspect(parser, input(' a')), [['a'], '']);
|
|
19
|
+
assert.deepStrictEqual(inspect(parser, input(' a')), [['a'], '']);
|
|
20
|
+
assert.deepStrictEqual(inspect(parser, input(' a')), [[' a'], '']);
|
|
21
|
+
assert.deepStrictEqual(inspect(parser, input(' a\n a')), [['a\na'], '']);
|
|
22
|
+
assert.deepStrictEqual(inspect(parser, input(' a\n a')), [['a\n a'], '']);
|
|
23
|
+
assert.deepStrictEqual(inspect(parser, input(' a\n a')), [['a\n a'], '']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser, input(' a\n a')), [['a'], ' a']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser, input(' \ta')), [['\ta'], '']);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser, input('\ta')), [['a'], '']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Parser, Result, Node } from '../parser';
|
|
2
|
+
import { some } from '../control/some';
|
|
3
|
+
import { always } from '../control/state';
|
|
4
|
+
import { block } from './block';
|
|
5
|
+
import { line } from './line';
|
|
6
|
+
import { match } from './match';
|
|
7
|
+
import { open } from './surround';
|
|
8
|
+
import { memoize } from 'spica/memoize';
|
|
9
|
+
|
|
10
|
+
export function indent<P extends Parser>(parser: P, separation?: boolean): P;
|
|
11
|
+
export function indent<P extends Parser>(opener: RegExp, parser: P, separation?: boolean): P;
|
|
12
|
+
export function indent(opener: RegExp | Parser<string>, parser: Parser<string> | boolean = false, separation = false): Parser<string> {
|
|
13
|
+
if (typeof opener === 'function') {
|
|
14
|
+
separation = parser as boolean;
|
|
15
|
+
parser = opener;
|
|
16
|
+
opener = / {1,4}|\t{1,2}/y;
|
|
17
|
+
}
|
|
18
|
+
assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^'));
|
|
19
|
+
assert(parser = parser as Parser<string>);
|
|
20
|
+
return always([
|
|
21
|
+
(input, output) => {
|
|
22
|
+
if (input.position === input.source.length) return Result.skip;
|
|
23
|
+
output.push();
|
|
24
|
+
return output.context;
|
|
25
|
+
},
|
|
26
|
+
block(match(opener, memoize(
|
|
27
|
+
([indent]) =>
|
|
28
|
+
some(open(indent, line(({ source }, output) =>
|
|
29
|
+
output.append(new Node(source))))),
|
|
30
|
+
([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)),
|
|
31
|
+
separation),
|
|
32
|
+
(input, output) => {
|
|
33
|
+
const source = trimBlockEnd(output.pop().foldl((acc, node) => acc + node.value, ''));
|
|
34
|
+
if (source === '') return Result.skip;
|
|
35
|
+
input.scope.focus(source);
|
|
36
|
+
return output.context;
|
|
37
|
+
},
|
|
38
|
+
parser,
|
|
39
|
+
(input, output) => {
|
|
40
|
+
input.scope.unfocus();
|
|
41
|
+
return output.context;
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function trimBlockEnd(block: string): string {
|
|
47
|
+
return block === ''
|
|
48
|
+
|| block.at(-1) !== '\n'
|
|
49
|
+
? block
|
|
50
|
+
: block.slice(0, block.at(-2) === '\r' ? -2 : -1);
|
|
51
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Parser } from '../parser';
|
|
2
|
+
|
|
3
|
+
export function lazy<P extends Parser>(builder: () => P): P;
|
|
4
|
+
export function lazy<T>(builder: () => Parser<T>): Parser<T> {
|
|
5
|
+
let parser: Parser<T> = (input, output) =>
|
|
6
|
+
(parser = builder())(input, output);
|
|
7
|
+
return (input, output) => parser(input, output);
|
|
8
|
+
}
|