securemark 0.285.0 → 0.286.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 (62) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/design.md +1 -1
  3. package/dist/index.js +185 -123
  4. package/markdown.d.ts +3 -4
  5. package/package.json +1 -1
  6. package/src/combinator/control/constraint/contract.ts +4 -4
  7. package/src/combinator/control/manipulation/convert.ts +3 -3
  8. package/src/combinator/control/manipulation/indent.ts +4 -4
  9. package/src/combinator/control/manipulation/match.ts +5 -3
  10. package/src/combinator/control/manipulation/scope.ts +7 -8
  11. package/src/combinator/control/manipulation/surround.ts +25 -13
  12. package/src/combinator/data/parser/context.test.ts +2 -2
  13. package/src/combinator/data/parser/context.ts +24 -7
  14. package/src/combinator/data/parser/some.ts +1 -0
  15. package/src/combinator/data/parser.ts +2 -1
  16. package/src/parser/api/parse.test.ts +18 -18
  17. package/src/parser/block/blockquote.ts +4 -4
  18. package/src/parser/block/extension/aside.ts +2 -2
  19. package/src/parser/block/extension/example.ts +2 -2
  20. package/src/parser/block/extension/figure.ts +1 -1
  21. package/src/parser/block/heading.ts +1 -1
  22. package/src/parser/block/ilist.ts +2 -2
  23. package/src/parser/block/olist.ts +6 -6
  24. package/src/parser/block/pagebreak.ts +1 -1
  25. package/src/parser/block/reply/cite.ts +3 -3
  26. package/src/parser/block/sidefence.ts +4 -4
  27. package/src/parser/block/table.ts +4 -4
  28. package/src/parser/block/ulist.ts +3 -3
  29. package/src/parser/block.ts +1 -1
  30. package/src/parser/context.ts +12 -7
  31. package/src/parser/header.ts +1 -1
  32. package/src/parser/inline/annotation.ts +4 -4
  33. package/src/parser/inline/autolink/email.ts +4 -4
  34. package/src/parser/inline/autolink/url.ts +15 -8
  35. package/src/parser/inline/bracket.ts +18 -16
  36. package/src/parser/inline/code.ts +3 -4
  37. package/src/parser/inline/deletion.ts +2 -2
  38. package/src/parser/inline/emphasis.ts +2 -2
  39. package/src/parser/inline/emstrong.ts +2 -2
  40. package/src/parser/inline/extension/index.ts +17 -13
  41. package/src/parser/inline/extension/indexer.ts +2 -3
  42. package/src/parser/inline/extension/label.ts +5 -5
  43. package/src/parser/inline/extension/placeholder.ts +2 -2
  44. package/src/parser/inline/html.ts +2 -2
  45. package/src/parser/inline/htmlentity.ts +4 -4
  46. package/src/parser/inline/insertion.ts +2 -2
  47. package/src/parser/inline/italic.ts +2 -2
  48. package/src/parser/inline/link.ts +5 -5
  49. package/src/parser/inline/mark.ts +2 -2
  50. package/src/parser/inline/math.ts +4 -4
  51. package/src/parser/inline/media.ts +13 -9
  52. package/src/parser/inline/reference.ts +6 -6
  53. package/src/parser/inline/remark.ts +2 -2
  54. package/src/parser/inline/ruby.ts +6 -6
  55. package/src/parser/inline/strong.ts +2 -2
  56. package/src/parser/inline/template.ts +10 -11
  57. package/src/parser/inline.test.ts +3 -0
  58. package/src/parser/source/escapable.ts +10 -5
  59. package/src/parser/source/str.ts +6 -6
  60. package/src/parser/source/text.ts +9 -5
  61. package/src/parser/source/unescapable.ts +9 -5
  62. package/src/parser/util.ts +5 -2
package/markdown.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Parser, Ctx } from './src/combinator/data/parser';
2
+ import { Command } from './src/parser/context';
2
3
  import { Dict } from 'spica/dict';
3
4
 
4
5
  declare abstract class Markdown<T> {
@@ -250,8 +251,6 @@ export namespace MarkdownParser {
250
251
  Parser<HTMLTableCellElement, Context, [
251
252
  SourceParser.StrParser,
252
253
  SourceParser.StrParser,
253
- SourceParser.StrParser,
254
- SourceParser.StrParser,
255
254
  ]> {
256
255
  }
257
256
  export type CellParser
@@ -1196,8 +1195,8 @@ export namespace MarkdownParser {
1196
1195
  }
1197
1196
  export interface BracketParser extends
1198
1197
  Inline<'url/bracket'>,
1199
- Parser<string, Context, [
1200
- Parser<string, Context, [
1198
+ Parser<string | Command.Escape, Context, [
1199
+ Parser<string | Command.Escape, Context, [
1201
1200
  BracketParser,
1202
1201
  SourceParser.UnescapableSourceParser,
1203
1202
  ]>,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.285.0",
3
+ "version": "0.286.0",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -1,8 +1,8 @@
1
1
  import { isArray } from 'spica/alias';
2
2
  import { Parser, Input, Ctx, Tree, Context, eval, exec, check } from '../../data/parser';
3
3
 
4
- //export function contract<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], parser: P, cond: (results: readonly Data<P>[], rest: string) => boolean): P;
5
- //export function contract<T>(patterns: string | RegExp | (string | RegExp)[], parser: Parser<T>, cond: (results: readonly T[], rest: string) => boolean): Parser<T> {
4
+ //export function contract<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], parser: P, cond: (nodes: readonly Data<P>[], rest: string) => boolean): P;
5
+ //export function contract<T>(patterns: string | RegExp | (string | RegExp)[], parser: Parser<T>, cond: (nodes: readonly T[], rest: string) => boolean): Parser<T> {
6
6
  // return verify(validate(patterns, parser), cond);
7
7
  //}
8
8
 
@@ -45,8 +45,8 @@ function guard<T>(f: (input: Input<Ctx>) => boolean, parser: Parser<T>): Parser<
45
45
  : undefined;
46
46
  }
47
47
 
48
- export function verify<P extends Parser<unknown>>(parser: P, cond: (results: readonly Tree<P>[], rest: string, context: Context<P>) => boolean): P;
49
- export function verify<T>(parser: Parser<T>, cond: (results: readonly T[], rest: string, context: Ctx) => boolean): Parser<T> {
48
+ export function verify<P extends Parser<unknown>>(parser: P, cond: (nodes: readonly Tree<P>[], rest: string, context: Context<P>) => boolean): P;
49
+ export function verify<T>(parser: Parser<T>, cond: (nodes: readonly T[], rest: string, context: Ctx) => boolean): Parser<T> {
50
50
  assert(parser);
51
51
  return input => {
52
52
  const { source, context } = input;
@@ -8,11 +8,11 @@ export function convert<T>(conv: (source: string, context: Ctx) => string, parse
8
8
  const src = conv(source, context);
9
9
  if (src === '') return empty ? [[], ''] : undefined;
10
10
  const sub = source.endsWith(src);
11
- const { logger } = context;
12
- context.logger = sub ? logger : {};
11
+ const { backtracks } = context;
12
+ context.backtracks = sub ? backtracks : {};
13
13
  const result = parser({ source: src, context });
14
14
  assert(check(src, result));
15
- context.logger = logger;
15
+ context.backtracks = backtracks;
16
16
  return result;
17
17
  };
18
18
  }
@@ -17,13 +17,13 @@ export function indent<T>(opener: RegExp | Parser<T>, parser?: Parser<T> | boole
17
17
  memoize(
18
18
  ([indent]) =>
19
19
  some(line(open(indent, ({ source }) => [[source], '']))),
20
- ([indent]) => indent.length * 2 + +(indent[0] === ' '), {})), separation),
20
+ ([indent]) => indent.length * 2 + +(indent[0] === ' '), {}), false), separation),
21
21
  (lines, rest, context) => {
22
22
  assert(parser = parser as Parser<T>);
23
- const { logger } = context;
24
- context.logger = {};
23
+ const { backtracks } = context;
24
+ context.backtracks = {};
25
25
  const result = parser({ source: trimBlockEnd(lines.join('')), context });
26
- context.logger = logger;
26
+ context.backtracks = backtracks;
27
27
  return result && exec(result) === ''
28
28
  ? [eval(result), rest]
29
29
  : undefined;
@@ -1,14 +1,16 @@
1
1
  import { Parser, exec, check } from '../../data/parser';
2
+ import { consume } from '../../../combinator';
2
3
 
3
- export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
4
- export function match<T>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<T>): Parser<T> {
4
+ export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P, cost?: boolean): P;
5
+ export function match<T>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<T>, cost = true): Parser<T> {
5
6
  assert(!pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^'));
6
7
  return input => {
7
- const { source } = input;
8
+ const { source, context } = input;
8
9
  if (source === '') return;
9
10
  const param = source.match(pattern);
10
11
  if (!param) return;
11
12
  assert(source.startsWith(param[0]));
13
+ cost && consume(param.length, context);
12
14
  const result = f(param)(input);
13
15
  assert(check(source, result, false));
14
16
  if (result === undefined) return;
@@ -1,7 +1,8 @@
1
1
  import { Parser, Context, eval, exec, check } from '../../data/parser';
2
+ import { consume } from '../../../combinator';
2
3
 
3
- export function focus<P extends Parser<unknown>>(scope: string | RegExp, parser: P): P;
4
- export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
4
+ export function focus<P extends Parser<unknown>>(scope: string | RegExp, parser: P, cost?: boolean): P;
5
+ export function focus<T>(scope: string | RegExp, parser: Parser<T>, cost = true): Parser<T> {
5
6
  assert(scope instanceof RegExp ? !scope.flags.match(/[gmy]/) && scope.source.startsWith('^') : scope);
6
7
  assert(parser);
7
8
  const match: (source: string) => string = typeof scope === 'string'
@@ -12,6 +13,7 @@ export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
12
13
  const src = match(source);
13
14
  assert(source.startsWith(src));
14
15
  if (src === '') return;
16
+ cost && consume(src.length, context);
15
17
  const offset = source.length - src.length;
16
18
  assert(offset >= 0);
17
19
  context.offset ??= 0;
@@ -35,14 +37,11 @@ export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T>
35
37
  return input => {
36
38
  const { source, context } = input;
37
39
  if (source === '') return;
38
- const { logger } = context;
39
- context.logger = {};
40
- //const { resources = { clock: 0 } } = context;
41
- //const clock = resources.clock;
40
+ const { backtracks } = context;
41
+ context.backtracks = {};
42
42
  const res1 = scope(input);
43
43
  assert(check(source, res1));
44
- //resources.clock = clock;
45
- context.logger = logger;
44
+ context.backtracks = backtracks;
46
45
  if (res1 === undefined || exec(res1).length >= source.length) return;
47
46
  const src = source.slice(0, source.length - exec(res1).length);
48
47
  assert(src !== '');
@@ -7,35 +7,40 @@ export function surround<P extends Parser<unknown>, S = string>(
7
7
  optional?: false,
8
8
  f?: (rss: [S[], SubTree<P>[], S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
9
9
  g?: (rss: [S[], SubTree<P>[], string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
10
- log?: number,
10
+ backtrack?: number,
11
+ bstate?: number,
11
12
  ): P;
12
13
  export function surround<P extends Parser<unknown>, S = string>(
13
14
  opener: string | RegExp | Parser<S, Context<P>>, parser: IntermediateParser<P>, closer: string | RegExp | Parser<S, Context<P>>,
14
15
  optional?: boolean,
15
16
  f?: (rss: [S[], SubTree<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
16
17
  g?: (rss: [S[], SubTree<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
17
- log?: number,
18
+ backtrack?: number,
19
+ bstate?: number,
18
20
  ): P;
19
21
  export function surround<P extends Parser<unknown>, S = string>(
20
22
  opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
21
23
  optional?: false,
22
24
  f?: (rss: [S[], Tree<P>[], S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
23
25
  g?: (rss: [S[], Tree<P>[], string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
24
- log?: number,
26
+ backtrack?: number,
27
+ bstate?: number,
25
28
  ): P;
26
29
  export function surround<P extends Parser<unknown>, S = string>(
27
30
  opener: string | RegExp | Parser<S, Context<P>>, parser: P, closer: string | RegExp | Parser<S, Context<P>>,
28
31
  optional?: boolean,
29
32
  f?: (rss: [S[], Tree<P>[] | undefined, S[]], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
30
33
  g?: (rss: [S[], Tree<P>[] | undefined, string], rest: string, context: Context<P>) => Result<Tree<P>, Context<P>, SubParsers<P>>,
31
- log?: number,
34
+ backtrack?: number,
35
+ bstate?: number,
32
36
  ): P;
33
37
  export function surround<T>(
34
38
  opener: string | RegExp | Parser<T>, parser: Parser<T>, closer: string | RegExp | Parser<T>,
35
39
  optional: boolean = false,
36
40
  f?: (rss: [T[], T[], T[]], rest: string, context: Ctx) => Result<T>,
37
41
  g?: (rss: [T[], T[], string], rest: string, context: Ctx) => Result<T>,
38
- log: number = 0,
42
+ backtrack: number = 0,
43
+ bstate: number = 0,
39
44
  ): Parser<T> {
40
45
  switch (typeof opener) {
41
46
  case 'string':
@@ -55,18 +60,23 @@ export function surround<T>(
55
60
  if (res1 === undefined) return;
56
61
  const rl = eval(res1);
57
62
  const mr_ = exec(res1);
58
- if (log & 1) {
59
- const { logger = {}, offset = 0 } = context;
63
+ if (backtrack & 1) {
64
+ const { backtracks = {}, backtrack: state = 0, offset = 0 } = context;
60
65
  for (let i = 0; i < source.length - mr_.length; ++i) {
61
66
  if (source[i] !== source[0]) break;
62
67
  const pos = source.length + offset - i - 1;
63
- if (!(pos in logger)) continue;
64
- assert(log >>> 2);
65
- if (logger[pos] & 1 << (log >>> 2)) return;
68
+ if (!(pos in backtracks)) continue;
69
+ assert(backtrack >>> 2);
70
+ // bracket only
71
+ const shift = backtrack >>> 2 === state >>> 2 ? state & 3 : 0;
72
+ if (backtracks[pos] & 1 << (backtrack >>> 2) + shift) return;
66
73
  }
67
74
  }
75
+ const { backtrack: state = 0 } = context;
76
+ context.backtrack = state | bstate;
68
77
  const res2 = mr_ !== '' ? parser({ source: mr_, context }) : undefined;
69
78
  assert(check(mr_, res2));
79
+ context.backtrack = state;
70
80
  const rm = eval(res2);
71
81
  const r_ = exec(res2, mr_);
72
82
  if (!rm && !optional) return;
@@ -75,9 +85,11 @@ export function surround<T>(
75
85
  const rr = eval(res3);
76
86
  const rest = exec(res3, r_);
77
87
  if (rest.length === lmr_.length) return;
78
- if (log & 2 && rr === undefined) {
79
- const { logger = {}, offset = 0 } = context;
80
- logger[source.length + offset - 1] |= 1 << (log >>> 2);
88
+ if (backtrack & 2 && rr === undefined) {
89
+ const { backtracks = {}, backtrack: state = 0, offset = 0 } = context;
90
+ // bracket only
91
+ const shift = backtrack >>> 2 === state >>> 2 ? state & 3 : 0;
92
+ backtracks[source.length + offset - 1] |= 1 << (backtrack >>> 2) + shift;
81
93
  }
82
94
  return rr
83
95
  ? f
@@ -9,7 +9,7 @@ describe('Unit: combinator/data/parser/context', () => {
9
9
  }
10
10
 
11
11
  describe('reset', () => {
12
- const parser: Parser<number> = some(creation(1, 1,
12
+ const parser: Parser<number> = some(creation(1,
13
13
  ({ source, context }) => [[context.resources?.clock ?? NaN], source.slice(1)]));
14
14
 
15
15
  it('root', () => {
@@ -36,7 +36,7 @@ describe('Unit: combinator/data/parser/context', () => {
36
36
  });
37
37
 
38
38
  describe('context', () => {
39
- const parser: Parser<boolean, Context> = some(creation(1, 1,
39
+ const parser: Parser<boolean, Context> = some(creation(1,
40
40
  ({ source, context }) => [[context.status!], source.slice(1)]));
41
41
 
42
42
  it('', () => {
@@ -25,7 +25,7 @@ export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
25
25
  function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: readonly [string, unknown][], values: unknown[], reset?: boolean): Result<Tree<P>>;
26
26
  function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: readonly [string, unknown][], values: unknown[], reset = false): Result<T> {
27
27
  if (reset) {
28
- context.logger = {};
28
+ context.backtracks = {};
29
29
  }
30
30
  for (let i = 0; i < changes.length; ++i) {
31
31
  const change = changes[i];
@@ -58,23 +58,40 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: read
58
58
  return result;
59
59
  }
60
60
 
61
- export function creation<P extends Parser<unknown>>(cost: number, recursion: number, parser: P): P;
62
- export function creation(cost: number, recursion: number, parser: Parser<unknown>): Parser<unknown> {
61
+ export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
62
+ export function creation(cost: number, parser: Parser<unknown>): Parser<unknown> {
63
63
  assert(cost >= 0);
64
- assert(recursion >= 0);
65
64
  return input => {
66
65
  const { context } = input;
67
66
  const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
68
67
  const { recursions } = resources;
69
68
  assert(recursions.length > 0);
69
+ const result = parser(input);
70
+ if (result === undefined) return;
71
+ consume(cost, context);
72
+ return result;
73
+ };
74
+ }
75
+ export function consume(cost: number, context: Ctx): void {
76
+ const { resources } = context;
77
+ if (!resources) return;
78
+ if (resources.clock < cost) throw new Error('Too many creations');
79
+ resources.clock -= cost;
80
+ }
81
+
82
+ export function recursion<P extends Parser<unknown>>(recursion: number, parser: P): P;
83
+ export function recursion(recursion: number, parser: Parser<unknown>): Parser<unknown> {
84
+ assert(recursion >= 0);
85
+ return input => {
86
+ const { context } = input;
87
+ const resources = context.resources ?? { clock: 1, recursions: [1] };
88
+ const { recursions } = resources;
89
+ assert(recursions.length > 0);
70
90
  const rec = min(recursion, recursions.length);
71
91
  if (rec > 0 && recursions[rec - 1] < 1) throw new Error('Too much recursion');
72
92
  rec > 0 && --recursions[rec - 1];
73
93
  const result = parser(input);
74
94
  rec > 0 && ++recursions[rec - 1];
75
- if (result === undefined) return;
76
- if (resources.clock < cost) throw new Error('Too many creations');
77
- resources.clock -= cost;
78
95
  return result;
79
96
  };
80
97
  }
@@ -18,6 +18,7 @@ export function some<T>(parser: Parser<T>, end?: string | RegExp | number, delim
18
18
  }));
19
19
  return ({ source, context }) => {
20
20
  if (source === '') return;
21
+ assert(context.backtracks ??= {});
21
22
  let rest = source;
22
23
  let nodes: T[] | undefined;
23
24
  if (delims.length > 0) {
@@ -19,7 +19,8 @@ export interface Ctx {
19
19
  precedence?: number;
20
20
  delimiters?: Delimiters;
21
21
  state?: number;
22
- logger?: Record<number, number>;
22
+ backtracks?: Record<number, number>;
23
+ backtrack?: number;
23
24
  }
24
25
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
25
26
  export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown, Ctx, infer D> ? D : never;
@@ -297,26 +297,26 @@ describe('Unit: parser/api/parse', () => {
297
297
  `<pre class="error" translate="no">${'{'.repeat(21)}a</pre>`,
298
298
  ]);
299
299
  assert.deepStrictEqual(
300
- [...parse(`${'('.repeat(22)}a`).children].map(el => el.outerHTML),
301
- [`<p>${'('.repeat(22)}a</p>`]);
300
+ [...parse(`${'('.repeat(20)}a`).children].map(el => el.outerHTML),
301
+ [`<p>${'('.repeat(20)}a</p>`]);
302
302
  assert.deepStrictEqual(
303
- [...parse(`${'('.repeat(23)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
303
+ [...parse(`${'('.repeat(21)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
304
304
  [
305
305
  '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
306
- `<pre class="error" translate="no">${'('.repeat(23)}a</pre>`,
306
+ `<pre class="error" translate="no">${'('.repeat(21)}a</pre>`,
307
307
  ]);
308
308
  assert.deepStrictEqual(
309
- [...parse(`${'['.repeat(23)}a`).children].map(el => el.outerHTML),
310
- [`<p>${'['.repeat(23)}a</p>`]);
309
+ [...parse(`${'['.repeat(20)}a`).children].map(el => el.outerHTML),
310
+ [`<p>${'['.repeat(20)}a</p>`]);
311
311
  assert.deepStrictEqual(
312
- [...parse(`${'['.repeat(24)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
312
+ [...parse(`${'['.repeat(21)}a`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
313
313
  [
314
314
  '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
315
- `<pre class="error" translate="no">${'['.repeat(24)}a</pre>`,
315
+ `<pre class="error" translate="no">${'['.repeat(21)}a</pre>`,
316
316
  ]);
317
317
  assert.deepStrictEqual(
318
- [...parse(`${'['.repeat(22)}\na`).children].map(el => el.outerHTML),
319
- [`<p>${'['.repeat(22)}<br>a</p>`]);
318
+ [...parse(`${'['.repeat(20)}\na`).children].map(el => el.outerHTML),
319
+ [`<p>${'['.repeat(20)}<br>a</p>`]);
320
320
  });
321
321
 
322
322
  it('recovery', () => {
@@ -334,14 +334,14 @@ describe('Unit: parser/api/parse', () => {
334
334
  it('creation', function () {
335
335
  this.timeout(5000);
336
336
  assert.deepStrictEqual(
337
- [...parse('.'.repeat(20000)).children].map(el => el.outerHTML),
338
- [`<p>${'.'.repeat(20000)}</p>`]);
337
+ [...parse('.'.repeat(100000)).children].map(el => el.outerHTML),
338
+ [`<p>${'.'.repeat(100000)}</p>`]);
339
339
  });
340
340
 
341
- it('creation error', function () {
341
+ it.skip('creation error', function () {
342
342
  this.timeout(5000);
343
343
  assert.deepStrictEqual(
344
- [...parse('.'.repeat(20001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
344
+ [...parse('.'.repeat(100001)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
345
345
  [
346
346
  '<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
347
347
  `<pre class="error" translate="no">${'.'.repeat(1000 - 3)}...</pre>`,
@@ -351,17 +351,17 @@ describe('Unit: parser/api/parse', () => {
351
351
  it('backtrack', function () {
352
352
  this.timeout(5000);
353
353
  assert.deepStrictEqual(
354
- [...parse(`(({{${'['.repeat(19)}${'.'.repeat(3301)}`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
355
- [`<p>(({{${'['.repeat(19)}${'.'.repeat(3301)}</p>`]);
354
+ [...parse(`..((${'['.repeat(16)}http://{{${'.'.repeat(5876)}`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
355
+ [`<p>..((${'['.repeat(16)}http://{{${'.'.repeat(5876)}</p>`]);
356
356
  });
357
357
 
358
358
  it('backtrack error', function () {
359
359
  this.timeout(5000);
360
360
  assert.deepStrictEqual(
361
- [...parse(`(({{${'['.repeat(19)}${'.'.repeat(3302)}`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
361
+ [...parse(`..((${'['.repeat(16)}http://{{${'.'.repeat(5877)}`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
362
362
  [
363
363
  '<h1 id="error:rnd" class="error">Error: Too many creations</h1>',
364
- `<pre class="error" translate="no">(({{${'['.repeat(19)}${'.'.repeat(1000 - 4 - 19 - 3)}...</pre>`,
364
+ `<pre class="error" translate="no">..((${'['.repeat(16)}http://{{${'.'.repeat(1000 - 2 - 2 - 16 - 9 - 3)}...</pre>`,
365
365
  ]);
366
366
  });
367
367
 
@@ -1,6 +1,6 @@
1
1
  import { BlockquoteParser } from '../block';
2
2
  import { Recursion } from '../context';
3
- import { union, some, creation, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
3
+ import { union, some, creation, recursion, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
4
4
  import { autolink } from '../autolink';
5
5
  import { contentline } from '../source';
6
6
  import { parse } from '../api/parse';
@@ -20,7 +20,7 @@ const indent = block(open(opener, some(contentline, /^>(?:$|\s)/)), false);
20
20
  const unindent = (source: string) => source.replace(/(?<=^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '');
21
21
 
22
22
  const source: BlockquoteParser.SourceParser = lazy(() => fmap(
23
- some(creation(0, Recursion.blockquote, union([
23
+ some(recursion(Recursion.blockquote, union([
24
24
  rewrite(
25
25
  indent,
26
26
  convert(unindent, source, true)),
@@ -31,11 +31,11 @@ const source: BlockquoteParser.SourceParser = lazy(() => fmap(
31
31
  ns => [html('blockquote', ns)]));
32
32
 
33
33
  const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
34
- some(creation(0, Recursion.blockquote, union([
34
+ some(recursion(Recursion.blockquote, union([
35
35
  rewrite(
36
36
  indent,
37
37
  convert(unindent, markdown, true)),
38
- creation(10, Recursion.ignore,
38
+ creation(10,
39
39
  rewrite(
40
40
  some(contentline, opener),
41
41
  convert(unindent, ({ source, context }) => {
@@ -1,11 +1,11 @@
1
1
  import { ExtensionParser } from '../../block';
2
2
  import { Recursion } from '../../context';
3
- import { creation, block, validate, fence, fmap } from '../../../combinator';
3
+ import { recursion, block, validate, fence, fmap } from '../../../combinator';
4
4
  import { identity } from '../../inline/extension/indexee';
5
5
  import { parse } from '../../api/parse';
6
6
  import { html } from 'typed-dom/dom';
7
7
 
8
- export const aside: ExtensionParser.AsideParser = creation(0, Recursion.block, block(validate('~~~', fmap(
8
+ export const aside: ExtensionParser.AsideParser = recursion(Recursion.block, block(validate('~~~', fmap(
9
9
  fence(/^(~{3,})aside(?!\S)([^\n]*)(?:$|\n)/, 300),
10
10
  // Bug: Type mismatch between outer and inner.
11
11
  ([body, overflow, closer, opener, delim, param]: string[], _, context) => {
@@ -1,14 +1,14 @@
1
1
  import { ExtensionParser } from '../../block';
2
2
  import { Recursion } from '../../context';
3
3
  import { eval } from '../../../combinator/data/parser';
4
- import { creation, block, validate, fence, fmap } from '../../../combinator';
4
+ import { recursion, block, validate, fence, fmap } from '../../../combinator';
5
5
  import { mathblock } from '../mathblock';
6
6
  import { parse } from '../../api/parse';
7
7
  import { html } from 'typed-dom/dom';
8
8
 
9
9
  const opener = /^(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/;
10
10
 
11
- export const example: ExtensionParser.ExampleParser = creation(0, Recursion.block, block(validate('~~~', fmap(
11
+ export const example: ExtensionParser.ExampleParser = recursion(Recursion.block, block(validate('~~~', fmap(
12
12
  fence(opener, 300),
13
13
  // Bug: Type mismatch between outer and inner.
14
14
  ([body, overflow, closer, opener, delim, type = 'markdown', param]: string[], _, context) => {
@@ -42,7 +42,7 @@ export const segment: FigureParser.SegmentParser = block(match(
42
42
  ]),
43
43
  ]),
44
44
  closer),
45
- ([, fence]) => fence.length, {})));
45
+ ([, fence]) => fence.length, {}), false));
46
46
 
47
47
  export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
48
48
  convert(source => source.slice(source.match(/^~+(?:\w+\s+)?/)![0].length, source.trimEnd().lastIndexOf('\n')),
@@ -8,7 +8,7 @@ import { html, defrag } from 'typed-dom/dom';
8
8
 
9
9
  export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
10
10
  /^#+[^\S\n]+\S[^\n]*(?:\n#+(?!\S)[^\n]*)*(?:$|\n)/,
11
- some(line(({ source }) => [[source], ''])))));
11
+ some(line(({ source }) => [[source], ''])), false)));
12
12
 
13
13
  export const heading: HeadingParser = block(rewrite(segment,
14
14
  // その他の表示制御は各所のCSSで行う。
@@ -1,6 +1,6 @@
1
1
  import { IListParser } from '../block';
2
2
  import { Recursion } from '../context';
3
- import { union, inits, some, creation, block, line, validate, indent, open, trim, fallback, lazy, fmap } from '../../combinator';
3
+ import { union, inits, some, recursion, block, line, validate, indent, open, trim, fallback, lazy, fmap } from '../../combinator';
4
4
  import { ulist_, invalid, fillFirstLine } from './ulist';
5
5
  import { olist_ } from './olist';
6
6
  import { inline } from '../inline';
@@ -14,7 +14,7 @@ export const ilist: IListParser = lazy(() => block(validate(
14
14
 
15
15
  export const ilist_: IListParser = lazy(() => block(fmap(validate(
16
16
  /^[-+*](?:$|\s)/,
17
- some(creation(0, Recursion.listitem, union([
17
+ some(recursion(Recursion.listitem, union([
18
18
  fmap(fallback(
19
19
  inits([
20
20
  line(open(/^[-+*](?:$|\s)/, trim(visualize(trimBlank(lineable(some(inline))))), true)),
@@ -1,6 +1,6 @@
1
1
  import { OListParser } from '../block';
2
2
  import { Recursion } from '../context';
3
- import { union, inits, subsequence, some, creation, block, line, validate, indent, focus, open, match, trim, fallback, lazy, fmap } from '../../combinator';
3
+ import { union, inits, subsequence, some, recursion, block, line, validate, indent, focus, open, match, trim, fallback, lazy, fmap } from '../../combinator';
4
4
  import { ulist_, checkbox, invalid, fillFirstLine } from './ulist';
5
5
  import { ilist_ } from './ilist';
6
6
  import { inline, indexee, indexer, dataindex } from '../inline';
@@ -24,14 +24,14 @@ export const olist: OListParser = lazy(() => block(validate(
24
24
  export const olist_: OListParser = lazy(() => block(union([
25
25
  match(
26
26
  openers['.'],
27
- memoize(ms => list(type(ms[1]), '.'), ms => idx(ms[1]), [])),
27
+ memoize(ms => list(type(ms[1]), '.'), ms => idx(ms[1]), []), false),
28
28
  match(
29
29
  openers['('],
30
- memoize(ms => list(type(ms[1]), '('), ms => idx(ms[1]), [])),
30
+ memoize(ms => list(type(ms[1]), '('), ms => idx(ms[1]), []), false),
31
31
  ])));
32
32
 
33
33
  const list = (type: string, form: string): OListParser.ListParser => fmap(
34
- some(creation(0, Recursion.listitem, union([
34
+ some(recursion(Recursion.listitem, union([
35
35
  indexee(fmap(fallback(
36
36
  inits([
37
37
  line(open(heads[form], subsequence([
@@ -47,10 +47,10 @@ const list = (type: string, form: string): OListParser.ListParser => fmap(
47
47
  const heads = {
48
48
  '.': focus(
49
49
  openers['.'],
50
- ({ source }) => [[source.trimEnd().split('.', 1)[0] + '.'], '']),
50
+ ({ source }) => [[source.trimEnd().split('.', 1)[0] + '.'], ''], false),
51
51
  '(': focus(
52
52
  openers['('],
53
- ({ source }) => [[source.trimEnd().replace(/^\($/, '(1)').replace(/^\((\w+)$/, '($1)')], '']),
53
+ ({ source }) => [[source.trimEnd().replace(/^\($/, '(1)').replace(/^\((\w+)$/, '($1)')], ''], false),
54
54
  } as const;
55
55
 
56
56
  function idx(value: string): number {
@@ -4,4 +4,4 @@ import { html } from 'typed-dom/dom';
4
4
 
5
5
  export const pagebreak: PagebreakParser = block(line(focus(
6
6
  /^={3,}[^\S\n]*(?:$|\n)/,
7
- () => [[html('hr')], ''])));
7
+ () => [[html('hr')], ''], false)));
@@ -12,9 +12,9 @@ export const cite: ReplyParser.CiteParser = line(fmap(validate(
12
12
  anchor,
13
13
  // Subject page representation.
14
14
  // リンクの実装は後で検討
15
- focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
- focus(/^>>#\S*(?=\s*$)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
17
- focus(/^>>https?:\/\/(?:[[]|[^\p{C}\p{S}\p{P}\s])\S*(?=\s*$)/u, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
15
+ focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], ''], false),
16
+ focus(/^>>#\S*(?=\s*$)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], ''], false),
17
+ focus(/^>>https?:\/\/(?:[[]|[^\p{C}\p{S}\p{P}\s])\S*(?=\s*$)/u, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], ''], false),
18
18
  ]),
19
19
  ]))),
20
20
  ([el, quotes = '']: [HTMLElement, string?]) => [
@@ -1,13 +1,13 @@
1
1
  import { SidefenceParser } from '../block';
2
2
  import { Recursion } from '../context';
3
- import { union, some, creation, block, focus, rewrite, convert, lazy, fmap } from '../../combinator';
3
+ import { union, some, recursion, block, focus, rewrite, convert, lazy, fmap } from '../../combinator';
4
4
  import { autolink } from '../autolink';
5
5
  import { contentline } from '../source';
6
6
  import { html, define, defrag } from 'typed-dom/dom';
7
7
 
8
8
  export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
9
9
  /^(?=\|+(?:[^\S\n]|\n\|))(?:\|+(?:[^\S\n][^\n]*)?(?:$|\n))+$/,
10
- union([source])),
10
+ union([source]), false),
11
11
  ([el]) => [
12
12
  define(el, {
13
13
  class: 'invalid',
@@ -21,10 +21,10 @@ const opener = /^(?=\|\|+(?:$|\s))/;
21
21
  const unindent = (source: string) => source.replace(/(?<=^|\n)\|(?:[^\S\n]|(?=\|*(?:$|\s)))|\n$/g, '');
22
22
 
23
23
  const source: SidefenceParser.SourceParser = lazy(() => fmap(
24
- some(creation(0, Recursion.block, union([
24
+ some(recursion(Recursion.block, union([
25
25
  focus(
26
26
  /^(?:\|\|+(?:[^\S\n][^\n]*)?(?:$|\n))+/,
27
- convert(unindent, source, true)),
27
+ convert(unindent, source, true), false),
28
28
  rewrite(
29
29
  some(contentline, opener),
30
30
  convert(unindent, fmap(autolink, ns => [html('pre', defrag(ns))]), true)),
@@ -40,10 +40,10 @@ const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean):
40
40
  const align: AlignParser = fmap(open(
41
41
  '|',
42
42
  union([
43
- focus(/^:-+:/, () => [['center'], '']),
44
- focus(/^:-+/, () => [['start'], '']),
45
- focus(/^-+:/, () => [['end'], '']),
46
- focus(/^-+/, () => [[''], '']),
43
+ focus(/^:-+:?/, ({ source }) =>
44
+ [[source[source.length - 1] === ':' ? 'center' : 'start'], ''], false),
45
+ focus(/^-+:?/, ({ source }) =>
46
+ [[source[source.length - 1] === ':' ? 'end' : ''], ''], false),
47
47
  ])),
48
48
  ns => [html('td', defrag(ns))]);
49
49