@cosense-toolbox/parser 0.1.0-beta.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +794 -0
  3. package/dist/ast-CIKXwSl5.mjs +41 -0
  4. package/dist/ast-CIKXwSl5.mjs.map +1 -0
  5. package/dist/ast-uYWtkHwT.cjs +52 -0
  6. package/dist/ast-uYWtkHwT.cjs.map +1 -0
  7. package/dist/compile.cjs +265 -0
  8. package/dist/compile.cjs.map +1 -0
  9. package/dist/compile.d.cts +135 -0
  10. package/dist/compile.d.cts.map +1 -0
  11. package/dist/compile.d.mts +135 -0
  12. package/dist/compile.d.mts.map +1 -0
  13. package/dist/compile.mjs +256 -0
  14. package/dist/compile.mjs.map +1 -0
  15. package/dist/create-compiler-CvlndKOA.d.cts +23 -0
  16. package/dist/create-compiler-CvlndKOA.d.cts.map +1 -0
  17. package/dist/create-compiler-jdA408ny.d.mts +23 -0
  18. package/dist/create-compiler-jdA408ny.d.mts.map +1 -0
  19. package/dist/image-url-YcTed8tg.cjs +68 -0
  20. package/dist/image-url-YcTed8tg.cjs.map +1 -0
  21. package/dist/image-url-eQ6X-oN0.mjs +51 -0
  22. package/dist/image-url-eQ6X-oN0.mjs.map +1 -0
  23. package/dist/index.cjs +716 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +77 -0
  26. package/dist/index.d.cts.map +1 -0
  27. package/dist/index.d.mts +77 -0
  28. package/dist/index.d.mts.map +1 -0
  29. package/dist/index.mjs +709 -0
  30. package/dist/index.mjs.map +1 -0
  31. package/dist/plugin.cjs +0 -0
  32. package/dist/plugin.d.cts +4 -0
  33. package/dist/plugin.d.mts +4 -0
  34. package/dist/plugin.mjs +1 -0
  35. package/dist/schema.cjs +171 -0
  36. package/dist/schema.cjs.map +1 -0
  37. package/dist/schema.d.cts +34 -0
  38. package/dist/schema.d.cts.map +1 -0
  39. package/dist/schema.d.mts +34 -0
  40. package/dist/schema.d.mts.map +1 -0
  41. package/dist/schema.mjs +148 -0
  42. package/dist/schema.mjs.map +1 -0
  43. package/dist/types-BXlnUFr0.d.mts +63 -0
  44. package/dist/types-BXlnUFr0.d.mts.map +1 -0
  45. package/dist/types-Bo_BrmvK.d.cts +230 -0
  46. package/dist/types-Bo_BrmvK.d.cts.map +1 -0
  47. package/dist/types-Bo_BrmvK.d.mts +230 -0
  48. package/dist/types-Bo_BrmvK.d.mts.map +1 -0
  49. package/dist/types-DVwlUtla.d.cts +63 -0
  50. package/dist/types-DVwlUtla.d.cts.map +1 -0
  51. package/dist/utils.cjs +80 -0
  52. package/dist/utils.cjs.map +1 -0
  53. package/dist/utils.d.cts +46 -0
  54. package/dist/utils.d.cts.map +1 -0
  55. package/dist/utils.d.mts +46 -0
  56. package/dist/utils.d.mts.map +1 -0
  57. package/dist/utils.mjs +72 -0
  58. package/dist/utils.mjs.map +1 -0
  59. package/package.json +96 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,716 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_image_url = require("./image-url-YcTed8tg.cjs");
3
+ let effect = require("effect");
4
+ //#region src/core/position.ts
5
+ const originOfLine = (line, offset) => ({
6
+ line,
7
+ column: 0,
8
+ offset
9
+ });
10
+ /** Origin を `by` 文字ぶん進める (部分文字列を再帰的に走査するときに使う)。 */
11
+ const shiftOrigin = (origin, by) => ({
12
+ line: origin.line,
13
+ column: origin.column + by,
14
+ offset: origin.offset + by
15
+ });
16
+ const pointAt = (origin, index) => ({
17
+ line: origin.line,
18
+ column: origin.column + index,
19
+ offset: origin.offset + index
20
+ });
21
+ /** 走査対象の `[start, end)` が占める Position。end は exclusive。 */
22
+ const spanAt = (origin, start, end) => ({
23
+ start: pointAt(origin, start),
24
+ end: pointAt(origin, end)
25
+ });
26
+ //#endregion
27
+ //#region src/inline/constructs/bare-url.ts
28
+ const URL_RE = /^https?:\/\/[^\s\]]+/i;
29
+ /**
30
+ * 角括弧で囲まれていない URL。常に外部リンクになり、画像 URL でも画像にはしない
31
+ * (本家準拠。インライン画像になるのは `[https://.../x.png]` の角括弧つきのみ)。
32
+ */
33
+ const bareUrlConstruct = (source, index) => {
34
+ const head = source[index];
35
+ if (head !== "h" && head !== "H") return effect.Option.none();
36
+ const match = source.slice(index).match(URL_RE);
37
+ if (!match) return effect.Option.none();
38
+ const url = match[0];
39
+ return effect.Option.some({
40
+ node: {
41
+ type: "externalLink",
42
+ label: url,
43
+ target: url
44
+ },
45
+ length: url.length
46
+ });
47
+ };
48
+ //#endregion
49
+ //#region src/core/scan.ts
50
+ /**
51
+ * scan.ts — 記法の知識を持たない文字列走査のプリミティブ。
52
+ */
53
+ /** 行頭の空白 (半角スペース / タブ / 全角スペース)。インデント判定の単一ソース。 */
54
+ const LEADING_WHITESPACE_RE = /^[ \t ]*/;
55
+ const leadingWhitespace = (s) => LEADING_WHITESPACE_RE.exec(s)?.[0] ?? "";
56
+ /**
57
+ * `openIdx` の `[` に対応する `]` の位置。深さを数えるので
58
+ * `[* a [b] c]` のような入れ子でも外側の `]` を返す。対応が閉じなければ None。
59
+ */
60
+ const findClosingBracket = (s, openIdx) => {
61
+ let depth = 0;
62
+ for (let i = openIdx; i < s.length; i++) {
63
+ const ch = s[i];
64
+ if (ch === "[") depth++;
65
+ else if (ch === "]") {
66
+ depth--;
67
+ if (depth === 0) return effect.Option.some(i);
68
+ }
69
+ }
70
+ return effect.Option.none();
71
+ };
72
+ /** `#` がタグの開始位置か (行頭、または直前が空白)。単語の途中の `#` を除くため。 */
73
+ const isTagBoundary = (s, index) => {
74
+ if (index === 0) return true;
75
+ const prev = s[index - 1];
76
+ return prev === " " || prev === " " || prev === " ";
77
+ };
78
+ //#endregion
79
+ //#region src/inline/bracket-rules/decoration.ts
80
+ /** 先頭の装飾記号の run と、空白を挟んだ中身。 */
81
+ const DECORATION_RE = /^([*/\-_]+)\s+([\s\S]+)$/;
82
+ const MAX_SIZE_LEVEL = 4;
83
+ /**
84
+ * `[* 太字]` `[/ 斜体]` `[- 打消し]` `[_ 下線]` とその複合 (`[-/ x]`)。
85
+ *
86
+ * 中身はリンクやアイコンとして再帰的に解釈するが、**装飾の入れ子は不可**
87
+ * (本家準拠)。そのため子の走査は allowDecoration=false で行う。
88
+ * 例: `[* [* 太字]ですね]` の内側は装飾ではなく内部リンクになる。
89
+ */
90
+ const decorationRule = (inner, ctx) => {
91
+ if (!ctx.allowDecoration) return effect.Option.none();
92
+ const match = inner.match(DECORATION_RE);
93
+ if (!match) return effect.Option.none();
94
+ const marks = match[1] ?? "";
95
+ const value = match[2] ?? "";
96
+ const stars = (marks.match(/\*/g) ?? []).length;
97
+ const valueOffset = inner.length - value.length;
98
+ return effect.Option.some({
99
+ type: "decoration",
100
+ value,
101
+ bold: stars > 0,
102
+ italic: marks.includes("/"),
103
+ strike: marks.includes("-"),
104
+ underline: marks.includes("_"),
105
+ sizeLevel: Math.min(Math.max(stars - 1, 0), MAX_SIZE_LEVEL),
106
+ children: ctx.tokenize(value, shiftOrigin(ctx.innerOrigin, valueOffset), false)
107
+ });
108
+ };
109
+ //#endregion
110
+ //#region src/inline/bracket-rules/formula.ts
111
+ /** `[$ x^2]` — 中身は解釈せず生のまま返す (KaTeX 等に渡す想定)。 */
112
+ const formulaRule = (inner) => inner.startsWith("$") ? effect.Option.some({
113
+ type: "formula",
114
+ value: inner.slice(1).trim()
115
+ }) : effect.Option.none();
116
+ //#endregion
117
+ //#region src/inline/bracket-rules/icon.ts
118
+ const ICON_RE = /^(.+)\.icon(?:\*(\d+))?$/;
119
+ const MIN_COUNT = 1;
120
+ const MAX_COUNT = 20;
121
+ /** `[user.icon]` と連打 `[user.icon*5]`。個数は 1..20 に収める (描画の暴走を防ぐため)。 */
122
+ const iconRule = (inner) => {
123
+ const match = inner.match(ICON_RE);
124
+ if (!match) return effect.Option.none();
125
+ const raw = match[2];
126
+ const count = raw ? Math.min(Math.max(Number.parseInt(raw, 10), MIN_COUNT), MAX_COUNT) : MIN_COUNT;
127
+ return effect.Option.some({
128
+ type: "icon",
129
+ user: match[1] ?? "",
130
+ count
131
+ });
132
+ };
133
+ //#endregion
134
+ //#region src/inline/bracket-rules/image-extension.ts
135
+ /**
136
+ * URL ではなく拡張子だけで画像と分かる中身 (`[a.png]`)。
137
+ *
138
+ * 装飾の中では画像にせずリンク扱いにする (本家準拠)。`[* [a.png]]` の内側は
139
+ * 画像ではなく `a.png` というタイトルの内部リンクになる。
140
+ */
141
+ const imageExtensionRule = (inner, ctx) => ctx.allowDecoration && require_image_url.hasImageExtension(inner) ? effect.Option.some({
142
+ type: "image",
143
+ src: inner
144
+ }) : effect.Option.none();
145
+ //#endregion
146
+ //#region src/inline/bracket-rules/internal-link.ts
147
+ /**
148
+ * `[title]` — 他のどのルールにも当たらなかった角括弧は内部リンクになる。
149
+ * 常に Some を返す catch-all なので、ルール配列の最後に置くこと。
150
+ */
151
+ const internalLinkRule = (inner) => effect.Option.some({
152
+ type: "internalLink",
153
+ label: inner,
154
+ target: inner
155
+ });
156
+ //#endregion
157
+ //#region src/inline/bracket-rules/project-link.ts
158
+ /**
159
+ * `[/project/title]` — 別プロジェクトのページへのリンク。
160
+ *
161
+ * タイトルは `/` を含みうるので最初の `/` だけで分割する。
162
+ * `[/project]` のようにタイトルが無い書き方も記法としては成立するので、
163
+ * その場合は title を空文字にする (利用側が「プロジェクトそのものへのリンク」と判断できる)。
164
+ */
165
+ const projectLinkRule = (inner) => {
166
+ if (!inner.startsWith("/")) return effect.Option.none();
167
+ const rest = inner.slice(1);
168
+ const slash = rest.indexOf("/");
169
+ const project = slash < 0 ? rest : rest.slice(0, slash);
170
+ const title = slash < 0 ? "" : rest.slice(slash + 1);
171
+ return effect.Option.some({
172
+ type: "projectLink",
173
+ label: inner,
174
+ target: inner,
175
+ project,
176
+ title
177
+ });
178
+ };
179
+ //#endregion
180
+ //#region src/inline/bracket-rules/url.ts
181
+ const URLS_RE = /https?:\/\/[^\s\]]+/gi;
182
+ /** 画像 URL が複数あるとき最後のものを採るのは本家の挙動。 */
183
+ const lastImage = (urls) => {
184
+ const images = urls.filter(require_image_url.isImageUrl);
185
+ return effect.Option.fromNullable(images[images.length - 1]);
186
+ };
187
+ /** 画像に添える遷移先。画像自身とは別の URL を優先し、無ければ先頭を使う。 */
188
+ const linkFor = (urls, src) => effect.Option.fromNullable(urls.find((url) => url !== src) ?? urls[0]);
189
+ /**
190
+ * 中身に URL を含む角括弧。本家の挙動に合わせて次の順で決める:
191
+ *
192
+ * 1. URL 以外の文字が残る → ラベル付き外部リンク。URL が画像でも文字リンクにする
193
+ * (`[ラベル https://x/a.png]` は画像にならない)。
194
+ * 2. URL だけが 2 つ以上:
195
+ * - 画像 URL があれば「リンク付き画像」。画像が複数なら最後を表示し、それ以外の先頭をリンク先にする。
196
+ * - 画像が無ければ 先頭 = リンク先 / 2 番目 = 表示テキスト。
197
+ * 3. URL が 1 つだけ → 画像なら画像、違えば裸の外部リンク。
198
+ */
199
+ const urlRule = (inner) => {
200
+ const urls = inner.match(URLS_RE) ?? [];
201
+ if (urls.length === 0) return effect.Option.none();
202
+ const label = urls.reduce((rest, url) => rest.replace(url, " "), inner).trim();
203
+ if (label !== "") return effect.Option.some({
204
+ type: "externalLink",
205
+ label,
206
+ target: urls[0] ?? inner
207
+ });
208
+ if (urls.length >= 2) return effect.Option.some((0, effect.pipe)(lastImage(urls), effect.Option.match({
209
+ onSome: (src) => (0, effect.pipe)(linkFor(urls, src), effect.Option.match({
210
+ onNone: () => ({
211
+ type: "image",
212
+ src
213
+ }),
214
+ onSome: (link) => ({
215
+ type: "image",
216
+ src,
217
+ link
218
+ })
219
+ })),
220
+ onNone: () => ({
221
+ type: "externalLink",
222
+ label: urls[1] ?? urls[0] ?? "",
223
+ target: urls[0] ?? inner
224
+ })
225
+ })));
226
+ const only = urls[0] ?? inner;
227
+ return effect.Option.some((0, effect.pipe)(effect.Option.liftPredicate(only, require_image_url.isImageUrl), effect.Option.match({
228
+ onSome: (src) => ({
229
+ type: "image",
230
+ src
231
+ }),
232
+ onNone: () => ({
233
+ type: "externalLink",
234
+ label: only,
235
+ target: only
236
+ })
237
+ })));
238
+ };
239
+ //#endregion
240
+ //#region src/inline/bracket-rules/index.ts
241
+ /** 中身に角括弧を含んでいても成立しうるルール。 */
242
+ const bracketRules = [formulaRule, decorationRule];
243
+ /**
244
+ * 「単純ターゲット」のルール。中身に `[` / `]` を含むときは試さない (本家準拠)。
245
+ * これにより `[[そうね] ですね]` の外側は記法にならず、先頭の `[` が素の文字になる。
246
+ * 末尾の internalLinkRule は常に成立する catch-all。
247
+ */
248
+ const simpleTargetRules = [
249
+ iconRule,
250
+ urlRule,
251
+ imageExtensionRule,
252
+ projectLinkRule,
253
+ internalLinkRule
254
+ ];
255
+ //#endregion
256
+ //#region src/inline/constructs/bracket.ts
257
+ /** ルールを順に試す。ジェネレータにしているのは最初に成立した時点で残りを評価しないため。 */
258
+ function* attempts$1(rules, inner, ctx) {
259
+ for (const rule of rules) yield rule(inner, ctx);
260
+ }
261
+ const parseInner = (inner, ctx) => (0, effect.pipe)(effect.Option.firstSomeOf(attempts$1(ctx.bracketRules, inner, ctx)), effect.Option.orElse(() => effect.Option.firstSomeOf(attempts$1(bracketRules, inner, ctx))), effect.Option.orElse(() => inner.includes("[") || inner.includes("]") ? effect.Option.none() : effect.Option.firstSomeOf(attempts$1(simpleTargetRules, inner, ctx))));
262
+ /**
263
+ * `[...]` 全般。閉じ括弧は深さを数えて探すので `[* a [b] c]` でも外側で閉じる。
264
+ *
265
+ * 中身が空 (`[]` / `[ ]`) のときと、どのルールにも当たらなかったときは None を返す。
266
+ * 呼び出し側は先頭の `[` を素の文字として 1 文字進めるので、内側が改めて走査される。
267
+ */
268
+ const bracketConstruct = (source, index, ctx) => {
269
+ if (source[index] !== "[") return effect.Option.none();
270
+ return (0, effect.pipe)(findClosingBracket(source, index), effect.Option.flatMap((end) => {
271
+ const inner = source.slice(index + 1, end);
272
+ if (inner.trim() === "") return effect.Option.none();
273
+ const innerCtx = {
274
+ ...ctx,
275
+ innerOrigin: shiftOrigin(ctx.origin, index + 1)
276
+ };
277
+ return (0, effect.pipe)(parseInner(inner, innerCtx), effect.Option.map((node) => ({
278
+ node,
279
+ length: end + 1 - index
280
+ })));
281
+ }));
282
+ };
283
+ //#endregion
284
+ //#region src/inline/constructs/hashtag.ts
285
+ /** タグ名に使えない文字。空白と角括弧と `#` 自身で終わる。 */
286
+ const TAG_NAME_RE = /^[^\s[\]#]+/;
287
+ /** `#tag`。行頭または空白の直後でだけ成立する (単語の途中の `#` を拾わないため)。 */
288
+ const hashtagConstruct = (source, index) => {
289
+ if (source[index] !== "#" || !isTagBoundary(source, index)) return effect.Option.none();
290
+ const match = source.slice(index + 1).match(TAG_NAME_RE);
291
+ if (!match) return effect.Option.none();
292
+ const value = match[0];
293
+ return effect.Option.some({
294
+ node: {
295
+ type: "hashtag",
296
+ value
297
+ },
298
+ length: value.length + 1
299
+ });
300
+ };
301
+ //#endregion
302
+ //#region src/inline/constructs/inline-code.ts
303
+ /** バッククォートで囲んだインラインコード。閉じるバッククォートが無ければ成立しない。 */
304
+ const inlineCodeConstruct = (source, index) => {
305
+ if (source[index] !== "`") return effect.Option.none();
306
+ const end = source.indexOf("`", index + 1);
307
+ if (end < 0) return effect.Option.none();
308
+ return effect.Option.some({
309
+ node: {
310
+ type: "inlineCode",
311
+ value: source.slice(index + 1, end)
312
+ },
313
+ length: end + 1 - index
314
+ });
315
+ };
316
+ //#endregion
317
+ //#region src/inline/constructs/strong-bracket.ts
318
+ /**
319
+ * `[[...]]` — 本家の strong。`]]` で閉じるときだけ成立する
320
+ * (深さは数えない。`[[a] b]` のようなケースは bracketConstruct 側で処理される)。
321
+ *
322
+ * 中身が画像 URL なら大きい画像、そうでなければ太字装飾になる。
323
+ */
324
+ const strongBracketConstruct = (source, index, ctx) => {
325
+ if (source[index] !== "[" || source[index + 1] !== "[") return effect.Option.none();
326
+ const end = source.indexOf("]]", index + 2);
327
+ if (end < 0) return effect.Option.none();
328
+ const inner = source.slice(index + 2, end);
329
+ const length = end + 2 - index;
330
+ if (require_image_url.isImageUrl(inner)) {
331
+ const node = {
332
+ type: "image",
333
+ src: inner,
334
+ large: true
335
+ };
336
+ return effect.Option.some({
337
+ node,
338
+ length
339
+ });
340
+ }
341
+ return effect.Option.some({
342
+ node: {
343
+ type: "decoration",
344
+ value: inner,
345
+ bold: true,
346
+ italic: false,
347
+ strike: false,
348
+ underline: false,
349
+ sizeLevel: 0,
350
+ children: ctx.tokenize(inner, shiftOrigin(ctx.origin, index + 2), false)
351
+ },
352
+ length
353
+ });
354
+ };
355
+ //#endregion
356
+ //#region src/inline/constructs/index.ts
357
+ const inlineConstructs = [
358
+ strongBracketConstruct,
359
+ bracketConstruct,
360
+ inlineCodeConstruct,
361
+ hashtagConstruct,
362
+ bareUrlConstruct
363
+ ];
364
+ //#endregion
365
+ //#region src/inline/tokenize.ts
366
+ /**
367
+ * tokenize.ts — 行内の走査ループ。
368
+ *
369
+ * 1 文字ずつ進みながら construct を順に試し、成立したらノードにする。どれも成立しなければ
370
+ * その 1 文字はテキストとして貯めておき、次にノードが出たところ (と末尾) でまとめて
371
+ * text ノードにする。位置の付与はこのループだけが行う。
372
+ */
373
+ /** 拡張のルールを既定のルールの前に並べる。拡張が既定の記法を上書きできるのはこの順序による。 */
374
+ const resolveExtensions = (extensions) => {
375
+ if (extensions === void 0 || extensions.length === 0) return {
376
+ constructs: inlineConstructs,
377
+ bracketRules: []
378
+ };
379
+ const constructs = [];
380
+ const bracketRules = [];
381
+ for (const extension of extensions) {
382
+ if (extension.constructs) constructs.push(...extension.constructs);
383
+ if (extension.bracketRules) bracketRules.push(...extension.bracketRules);
384
+ }
385
+ return {
386
+ constructs: [...constructs, ...inlineConstructs],
387
+ bracketRules
388
+ };
389
+ };
390
+ /**
391
+ * ノード本体に位置を与える。判別共用体へのスプレッドは TS が型を保てないため
392
+ * ここだけキャストする (position 以外のフィールドは触っていない)。
393
+ */
394
+ const withPosition = (node, position) => ({
395
+ ...node,
396
+ position
397
+ });
398
+ /** ジェネレータにしているのは、最初に成立した時点で残りのルールを評価しないため。 */
399
+ function* attempts(constructs, source, index, ctx) {
400
+ for (const construct of constructs) yield construct(source, index, ctx);
401
+ }
402
+ const firstMatch = (constructs, source, index, ctx) => effect.Option.firstSomeOf(attempts(constructs, source, index, ctx));
403
+ const scan = (source, origin, allowDecoration, rules) => {
404
+ const out = [];
405
+ const ctx = {
406
+ allowDecoration,
407
+ origin,
408
+ bracketRules: rules.bracketRules,
409
+ tokenize: (inner, innerOrigin, innerAllowDecoration) => scan(inner, innerOrigin, innerAllowDecoration, rules)
410
+ };
411
+ let textStart = 0;
412
+ let index = 0;
413
+ const flushText = (end) => {
414
+ if (end <= textStart) return;
415
+ out.push({
416
+ type: "text",
417
+ value: source.slice(textStart, end),
418
+ position: spanAt(origin, textStart, end)
419
+ });
420
+ };
421
+ while (index < source.length) {
422
+ const match = firstMatch(rules.constructs, source, index, ctx);
423
+ if (effect.Option.isNone(match)) {
424
+ index++;
425
+ continue;
426
+ }
427
+ flushText(index);
428
+ const { node, length } = match.value;
429
+ out.push(withPosition(node, spanAt(origin, index, index + length)));
430
+ index += length;
431
+ textStart = index;
432
+ }
433
+ flushText(index);
434
+ return out;
435
+ };
436
+ const resolveOrigin = (origin) => ({
437
+ line: origin?.line ?? 0,
438
+ column: origin?.column ?? 0,
439
+ offset: origin?.offset ?? 0
440
+ });
441
+ /**
442
+ * インライン記法をノード列に分解する。改行を含まない 1 行分の文字列を渡すこと。
443
+ *
444
+ * 記法として成立しなかった文字は text ノードにまとまる。空文字列では空配列を返す。
445
+ */
446
+ const tokenizeInline = (source, options) => scan(source, resolveOrigin(options?.origin), options?.allowDecoration ?? true, resolveExtensions(options?.extensions));
447
+ /**
448
+ * 解決済みのルールで走査する内部向け入口。
449
+ * 行ごとに `tokenizeInline` を呼ぶと拡張の解決が毎回走るので、それを避けたいときに使う。
450
+ */
451
+ const tokenizeInlineWith = (source, origin, rules) => scan(source, origin, true, rules);
452
+ //#endregion
453
+ //#region src/block/classify.ts
454
+ /**
455
+ * classify.ts — 1 行が何の行かを判定する。インライン記法の中身には立ち入らない。
456
+ *
457
+ * 結果はタグ付きユニオンなので、行の種類を増やすと分岐している側が
458
+ * exhaustive チェックで対応漏れを教えてくれる。
459
+ */
460
+ const CODE_HEADER_RE = /^code:(.+)$/;
461
+ const TABLE_HEADER_RE = /^table:(.+)$/;
462
+ /** 引用記号と、その直後の空白 1 つ (あれば本文から除く)。 */
463
+ const QUOTE_RE = /^>\s?/;
464
+ const MONOSPACE_RE = /^[$%]/;
465
+ const codeHeader = (rest, indent) => {
466
+ const match = rest.match(CODE_HEADER_RE);
467
+ return match ? effect.Option.some({
468
+ _tag: "codeHeader",
469
+ indent,
470
+ filename: (match[1] ?? "").trimEnd()
471
+ }) : effect.Option.none();
472
+ };
473
+ const tableHeader = (rest, indent) => {
474
+ const match = rest.match(TABLE_HEADER_RE);
475
+ return match ? effect.Option.some({
476
+ _tag: "tableHeader",
477
+ indent,
478
+ name: (match[1] ?? "").trimEnd()
479
+ }) : effect.Option.none();
480
+ };
481
+ const content = (rest, indent) => {
482
+ const quoteMark = rest.match(QUOTE_RE)?.[0] ?? "";
483
+ const body = rest.slice(quoteMark.length);
484
+ return {
485
+ _tag: "content",
486
+ indent,
487
+ quote: quoteMark !== "",
488
+ monospace: MONOSPACE_RE.test(body),
489
+ contentOffset: indent + quoteMark.length
490
+ };
491
+ };
492
+ /**
493
+ * 行の役割を判定する。`code:` / `table:` は行の意味がページの文脈
494
+ * (コードブロックの中かどうか) で変わるので、ここでは「ヘッダの形をしている」ことだけを見る。
495
+ */
496
+ const classifyLine = (raw) => {
497
+ const indent = leadingWhitespace(raw).length;
498
+ const rest = raw.slice(indent);
499
+ return (0, effect.pipe)(effect.Option.firstSomeOf([codeHeader(rest, indent), tableHeader(rest, indent)]), effect.Option.getOrElse(() => content(rest, indent)));
500
+ };
501
+ /** 行頭の空白の文字数。全角スペースも 1 文字として数える。 */
502
+ const indentOf = (raw) => leadingWhitespace(raw).length;
503
+ //#endregion
504
+ //#region src/block/build.ts
505
+ /**
506
+ * build.ts — 行の並びをブロックに畳む。
507
+ *
508
+ * `code:` / `table:` は「ヘッダ行 + それより深いインデントの行」で 1 つのブロックになる。
509
+ * この複数行のまとまりを作るのがここの仕事で、行の中身の解釈は注入された tokenize に任せる
510
+ * (どの記法ルールを使うかを知らずに済むので、拡張入りのパーサーでもここは変わらない)。
511
+ */
512
+ const lineOrigin = (line) => originOfLine(line.index, line.offset);
513
+ const wholeLine = (line) => spanAt(lineOrigin(line), 0, line.text.length);
514
+ /** インデントを `amount` 文字ぶん浅くする。空白より多くは削らない。 */
515
+ const dedent = (text, amount) => text.slice(Math.min(amount, indentOf(text)));
516
+ const titleBlock = (line, tokenize) => ({
517
+ type: "title",
518
+ value: line.text,
519
+ children: tokenize(line.text, lineOrigin(line)),
520
+ position: wholeLine(line)
521
+ });
522
+ const lineBlock = (line, role, tokenize) => ({
523
+ type: "line",
524
+ indent: role.indent,
525
+ quote: role.quote,
526
+ monospace: role.monospace,
527
+ children: tokenize(line.text.slice(role.contentOffset), {
528
+ line: line.index,
529
+ column: role.contentOffset,
530
+ offset: line.offset + role.contentOffset
531
+ }),
532
+ position: wholeLine(line)
533
+ });
534
+ const codeLine = (line, headerIndent) => ({
535
+ type: "codeLine",
536
+ value: dedent(line.text, headerIndent + 1),
537
+ position: wholeLine(line)
538
+ });
539
+ const tableCells = (line) => {
540
+ const indent = indentOf(line.text);
541
+ const origin = lineOrigin(line);
542
+ const cells = [];
543
+ let start = indent;
544
+ for (const value of line.text.slice(indent).split(" ")) {
545
+ cells.push({
546
+ type: "tableCell",
547
+ value,
548
+ position: spanAt(origin, start, start + value.length)
549
+ });
550
+ start += value.length + 1;
551
+ }
552
+ return cells;
553
+ };
554
+ const tableRow = (line) => ({
555
+ type: "tableRow",
556
+ cells: tableCells(line),
557
+ position: wholeLine(line)
558
+ });
559
+ /** ヘッダより深いインデントが続く範囲の終端 (exclusive)。 */
560
+ const bodyEnd = (lines, start, headerIndent) => {
561
+ let end = start;
562
+ while (end < lines.length) {
563
+ const line = lines[end];
564
+ if (line === void 0 || indentOf(line.text) <= headerIndent) break;
565
+ end++;
566
+ }
567
+ return end;
568
+ };
569
+ const blockPosition = (header, body) => ({
570
+ start: wholeLine(header).start,
571
+ end: wholeLine(body[body.length - 1] ?? header).end
572
+ });
573
+ const codeBlock = (header, body, filename, indent) => ({
574
+ type: "codeBlock",
575
+ filename,
576
+ indent,
577
+ lines: body.map((line) => codeLine(line, indent)),
578
+ position: blockPosition(header, body)
579
+ });
580
+ const tableBlock = (header, body, name, indent) => ({
581
+ type: "table",
582
+ name,
583
+ indent,
584
+ rows: body.map(tableRow),
585
+ position: blockPosition(header, body)
586
+ });
587
+ /**
588
+ * 行の並びをブロックの並びに畳む。1 行目は無条件でタイトルになる
589
+ * (Cosense ではタイトル行が `code:` や `table:` として解釈されることはない)。
590
+ */
591
+ const buildBlocks = (lines, tokenize) => {
592
+ const blocks = [];
593
+ const head = lines[0];
594
+ if (head === void 0) return blocks;
595
+ blocks.push(titleBlock(head, tokenize));
596
+ let index = 1;
597
+ while (index < lines.length) {
598
+ const line = lines[index];
599
+ if (line === void 0) break;
600
+ index = effect.Match.value(classifyLine(line.text)).pipe(effect.Match.tag("codeHeader", (role) => {
601
+ const end = bodyEnd(lines, index + 1, role.indent);
602
+ blocks.push(codeBlock(line, lines.slice(index + 1, end), role.filename, role.indent));
603
+ return end;
604
+ }), effect.Match.tag("tableHeader", (role) => {
605
+ const end = bodyEnd(lines, index + 1, role.indent);
606
+ blocks.push(tableBlock(line, lines.slice(index + 1, end), role.name, role.indent));
607
+ return end;
608
+ }), effect.Match.tag("content", (role) => {
609
+ blocks.push(lineBlock(line, role, tokenize));
610
+ return index + 1;
611
+ }), effect.Match.exhaustive);
612
+ }
613
+ return blocks;
614
+ };
615
+ /**
616
+ * 1 行だけを通常行として組み立てる。
617
+ * ページの文脈が無いので `code:` / `table:` ヘッダもブロックにはせず、通常行として扱う。
618
+ */
619
+ const buildLineBlock = (line, tokenize) => {
620
+ const role = classifyLine(line.text);
621
+ return lineBlock(line, role._tag === "content" ? role : {
622
+ _tag: "content",
623
+ indent: role.indent,
624
+ quote: false,
625
+ monospace: false,
626
+ contentOffset: role.indent
627
+ }, tokenize);
628
+ };
629
+ //#endregion
630
+ //#region src/parse.ts
631
+ /**
632
+ * parse.ts — ページ全文の入口。
633
+ */
634
+ /**
635
+ * CR / CRLF を LF に揃える。
636
+ *
637
+ * `parse` は必ずこれを通してから解析するので、報告される位置は**正規化後**の
638
+ * 文字列を基準にする。CRLF のソースでは元のオフセットと 1 行につき 1 文字ずれる。
639
+ */
640
+ const normalizeLineEndings = (source) => source.replace(/\r\n?/g, "\n");
641
+ const toSourceLines = (source) => {
642
+ const lines = [];
643
+ let offset = 0;
644
+ for (const [index, text] of source.split("\n").entries()) {
645
+ lines.push({
646
+ index,
647
+ text,
648
+ offset
649
+ });
650
+ offset += text.length + 1;
651
+ }
652
+ return lines;
653
+ };
654
+ /**
655
+ * ページ全文をパースする。**失敗しない**: どんな入力でも Page を返す。
656
+ * 記法として成立しない部分は素のテキストになるだけで、エラーにはならない。
657
+ *
658
+ * 1 行目はタイトルとして扱われる (Cosense のページは 1 行目がタイトル)。
659
+ */
660
+ const parse = (source, options) => {
661
+ const normalized = normalizeLineEndings(source);
662
+ const lines = toSourceLines(normalized);
663
+ const rules = resolveExtensions(options?.extensions);
664
+ const last = lines[lines.length - 1];
665
+ return {
666
+ type: "page",
667
+ children: buildBlocks(lines, (text, origin) => tokenizeInlineWith(text, origin, rules)),
668
+ position: {
669
+ start: {
670
+ line: 0,
671
+ column: 0,
672
+ offset: 0
673
+ },
674
+ end: {
675
+ line: last?.index ?? 0,
676
+ column: last?.text.length ?? 0,
677
+ offset: normalized.length
678
+ }
679
+ }
680
+ };
681
+ };
682
+ /**
683
+ * 1 行だけを通常行としてパースする。
684
+ *
685
+ * `code:` / `table:` はページの文脈があって初めてブロックになるので、
686
+ * ここでは通常行として扱う。
687
+ */
688
+ const parseLine = (raw, options) => {
689
+ const rules = resolveExtensions(options?.extensions);
690
+ return buildLineBlock({
691
+ index: options?.line ?? 0,
692
+ text: raw,
693
+ offset: options?.offset ?? 0
694
+ }, (text, origin) => tokenizeInlineWith(text, origin, rules));
695
+ };
696
+ /**
697
+ * 拡張を固定したパーサーを作る。同じ拡張で何度もパースするときに、
698
+ * 呼び出しごとに options を渡さずに済む。
699
+ */
700
+ const createParser = (options) => ({
701
+ parse: (source) => parse(source, options),
702
+ parseLine: (raw, lineOptions) => parseLine(raw, {
703
+ ...lineOptions,
704
+ ...options
705
+ })
706
+ });
707
+ //#endregion
708
+ exports.asImageSrc = require_image_url.asImageSrc;
709
+ exports.createParser = createParser;
710
+ exports.isImageUrl = require_image_url.isImageUrl;
711
+ exports.normalizeLineEndings = normalizeLineEndings;
712
+ exports.parse = parse;
713
+ exports.parseLine = parseLine;
714
+ exports.tokenizeInline = tokenizeInline;
715
+
716
+ //# sourceMappingURL=index.cjs.map