securemark 0.258.9 → 0.259.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/design.md +6 -5
  3. package/dist/index.js +535 -278
  4. package/markdown.d.ts +4 -3
  5. package/package.json +1 -1
  6. package/src/combinator/control/constraint/block.test.ts +5 -5
  7. package/src/combinator/control/constraint/block.ts +3 -2
  8. package/src/combinator/control/constraint/contract.ts +7 -5
  9. package/src/combinator/control/constraint/line.test.ts +6 -6
  10. package/src/combinator/control/constraint/line.ts +6 -5
  11. package/src/combinator/control/manipulation/convert.ts +6 -5
  12. package/src/combinator/control/manipulation/fence.ts +2 -1
  13. package/src/combinator/control/manipulation/indent.test.ts +14 -14
  14. package/src/combinator/control/manipulation/indent.ts +5 -5
  15. package/src/combinator/control/manipulation/lazy.ts +2 -2
  16. package/src/combinator/control/manipulation/match.ts +3 -2
  17. package/src/combinator/control/manipulation/recovery.ts +6 -6
  18. package/src/combinator/control/manipulation/scope.ts +13 -10
  19. package/src/combinator/control/manipulation/surround.ts +9 -8
  20. package/src/combinator/control/manipulation/trim.test.ts +9 -9
  21. package/src/combinator/control/monad/bind.ts +3 -2
  22. package/src/combinator/data/parser/context/memo.ts +3 -4
  23. package/src/combinator/data/parser/context.test.ts +9 -9
  24. package/src/combinator/data/parser/context.ts +38 -34
  25. package/src/combinator/data/parser/inits.ts +3 -2
  26. package/src/combinator/data/parser/sequence.test.ts +10 -10
  27. package/src/combinator/data/parser/sequence.ts +3 -2
  28. package/src/combinator/data/parser/some.test.ts +13 -13
  29. package/src/combinator/data/parser/some.ts +3 -2
  30. package/src/combinator/data/parser/subsequence.test.ts +14 -14
  31. package/src/combinator/data/parser/union.test.ts +10 -10
  32. package/src/combinator/data/parser/union.ts +2 -2
  33. package/src/combinator/data/parser.ts +6 -1
  34. package/src/parser/api/bind.ts +3 -3
  35. package/src/parser/api/header.ts +1 -1
  36. package/src/parser/api/normalize.ts +1 -1
  37. package/src/parser/api/parse.ts +3 -3
  38. package/src/parser/autolink.test.ts +1 -1
  39. package/src/parser/autolink.ts +2 -2
  40. package/src/parser/block/blockquote.test.ts +1 -1
  41. package/src/parser/block/blockquote.ts +1 -1
  42. package/src/parser/block/codeblock.test.ts +1 -1
  43. package/src/parser/block/codeblock.ts +1 -1
  44. package/src/parser/block/dlist.test.ts +1 -1
  45. package/src/parser/block/extension/aside.test.ts +1 -1
  46. package/src/parser/block/extension/example.test.ts +1 -1
  47. package/src/parser/block/extension/example.ts +1 -1
  48. package/src/parser/block/extension/fig.test.ts +1 -1
  49. package/src/parser/block/extension/figbase.test.ts +1 -1
  50. package/src/parser/block/extension/figure.test.ts +1 -1
  51. package/src/parser/block/extension/figure.ts +1 -1
  52. package/src/parser/block/extension/message.test.ts +1 -1
  53. package/src/parser/block/extension/message.ts +1 -1
  54. package/src/parser/block/extension/placeholder.test.ts +1 -1
  55. package/src/parser/block/extension/table.test.ts +1 -1
  56. package/src/parser/block/extension/table.ts +1 -1
  57. package/src/parser/block/extension.test.ts +1 -1
  58. package/src/parser/block/heading.test.ts +3 -3
  59. package/src/parser/block/heading.ts +1 -1
  60. package/src/parser/block/horizontalrule.test.ts +1 -1
  61. package/src/parser/block/ilist.test.ts +1 -1
  62. package/src/parser/block/mathblock.test.ts +1 -1
  63. package/src/parser/block/olist.test.ts +1 -1
  64. package/src/parser/block/olist.ts +5 -5
  65. package/src/parser/block/paragraph.test.ts +1 -1
  66. package/src/parser/block/reply/cite.test.ts +4 -2
  67. package/src/parser/block/reply/cite.ts +2 -1
  68. package/src/parser/block/reply/quote.test.ts +1 -1
  69. package/src/parser/block/reply/quote.ts +2 -2
  70. package/src/parser/block/reply.test.ts +1 -1
  71. package/src/parser/block/sidefence.test.ts +1 -1
  72. package/src/parser/block/table.test.ts +1 -1
  73. package/src/parser/block/table.ts +1 -1
  74. package/src/parser/block/ulist.test.ts +1 -1
  75. package/src/parser/block/ulist.ts +1 -1
  76. package/src/parser/block.ts +2 -2
  77. package/src/parser/context.ts +15 -6
  78. package/src/parser/header.test.ts +1 -1
  79. package/src/parser/header.ts +3 -3
  80. package/src/parser/inline/annotation.test.ts +1 -1
  81. package/src/parser/inline/annotation.ts +3 -4
  82. package/src/parser/inline/autolink/account.test.ts +1 -1
  83. package/src/parser/inline/autolink/anchor.test.ts +1 -1
  84. package/src/parser/inline/autolink/channel.test.ts +1 -1
  85. package/src/parser/inline/autolink/email.test.ts +1 -1
  86. package/src/parser/inline/autolink/email.ts +1 -1
  87. package/src/parser/inline/autolink/hashnum.test.ts +1 -1
  88. package/src/parser/inline/autolink/hashtag.test.ts +1 -1
  89. package/src/parser/inline/autolink/url.test.ts +1 -1
  90. package/src/parser/inline/autolink.ts +1 -1
  91. package/src/parser/inline/bracket.test.ts +1 -1
  92. package/src/parser/inline/bracket.ts +8 -8
  93. package/src/parser/inline/code.test.ts +1 -1
  94. package/src/parser/inline/code.ts +2 -2
  95. package/src/parser/inline/comment.test.ts +1 -1
  96. package/src/parser/inline/comment.ts +2 -2
  97. package/src/parser/inline/deletion.test.ts +1 -1
  98. package/src/parser/inline/deletion.ts +2 -2
  99. package/src/parser/inline/emphasis.test.ts +1 -1
  100. package/src/parser/inline/emphasis.ts +2 -2
  101. package/src/parser/inline/emstrong.ts +4 -4
  102. package/src/parser/inline/escape.ts +3 -3
  103. package/src/parser/inline/extension/index.test.ts +1 -1
  104. package/src/parser/inline/extension/index.ts +3 -4
  105. package/src/parser/inline/extension/indexer.test.ts +1 -1
  106. package/src/parser/inline/extension/label.test.ts +1 -1
  107. package/src/parser/inline/extension/placeholder.test.ts +1 -1
  108. package/src/parser/inline/extension/placeholder.ts +2 -2
  109. package/src/parser/inline/html.test.ts +1 -1
  110. package/src/parser/inline/html.ts +2 -2
  111. package/src/parser/inline/htmlentity.test.ts +1 -1
  112. package/src/parser/inline/htmlentity.ts +1 -1
  113. package/src/parser/inline/insertion.test.ts +1 -1
  114. package/src/parser/inline/insertion.ts +2 -2
  115. package/src/parser/inline/link.test.ts +1 -1
  116. package/src/parser/inline/link.ts +14 -22
  117. package/src/parser/inline/mark.test.ts +1 -1
  118. package/src/parser/inline/mark.ts +2 -2
  119. package/src/parser/inline/math.test.ts +1 -1
  120. package/src/parser/inline/math.ts +1 -1
  121. package/src/parser/inline/media.test.ts +1 -1
  122. package/src/parser/inline/media.ts +3 -3
  123. package/src/parser/inline/reference.test.ts +1 -1
  124. package/src/parser/inline/reference.ts +3 -4
  125. package/src/parser/inline/ruby.test.ts +1 -1
  126. package/src/parser/inline/ruby.ts +5 -5
  127. package/src/parser/inline/shortmedia.test.ts +1 -1
  128. package/src/parser/inline/strong.test.ts +1 -1
  129. package/src/parser/inline/strong.ts +2 -2
  130. package/src/parser/inline/template.test.ts +1 -1
  131. package/src/parser/inline/template.ts +2 -2
  132. package/src/parser/inline.test.ts +1 -1
  133. package/src/parser/locale.test.ts +1 -1
  134. package/src/parser/segment.ts +1 -1
  135. package/src/parser/source/escapable.test.ts +1 -1
  136. package/src/parser/source/escapable.ts +1 -1
  137. package/src/parser/source/line.test.ts +1 -1
  138. package/src/parser/source/line.ts +2 -2
  139. package/src/parser/source/str.ts +4 -4
  140. package/src/parser/source/text.test.ts +1 -1
  141. package/src/parser/source/text.ts +4 -4
  142. package/src/parser/source/unescapable.test.ts +1 -1
  143. package/src/parser/source/unescapable.ts +1 -1
  144. package/src/parser/visibility.ts +13 -13
  145. package/src/util/quote.ts +1 -1
package/markdown.d.ts CHANGED
@@ -577,6 +577,7 @@ export namespace MarkdownParser {
577
577
  InlineParser.AutolinkParser.AnchorParser,
578
578
  Parser<HTMLAnchorElement, Context, []>,
579
579
  Parser<HTMLAnchorElement, Context, []>,
580
+ Parser<HTMLAnchorElement, Context, []>,
580
581
  ]>,
581
582
  ]> {
582
583
  }
@@ -842,7 +843,7 @@ export namespace MarkdownParser {
842
843
  // { uri }
843
844
  // [abc]{uri nofollow}
844
845
  Inline<'link'>,
845
- Parser<HTMLElement, Context, [
846
+ Parser<HTMLAnchorElement, Context, [
846
847
  LinkParser.MediaLinkParser,
847
848
  LinkParser.TextLinkParser,
848
849
  ]> {
@@ -850,7 +851,7 @@ export namespace MarkdownParser {
850
851
  export namespace LinkParser {
851
852
  export interface TextLinkParser extends
852
853
  Inline<'link/textlink'>,
853
- Parser<HTMLElement, Context, [
854
+ Parser<HTMLAnchorElement, Context, [
854
855
  Parser<(HTMLElement | string)[], Context, [
855
856
  InlineParser,
856
857
  ]>,
@@ -859,7 +860,7 @@ export namespace MarkdownParser {
859
860
  }
860
861
  export interface MediaLinkParser extends
861
862
  Inline<'link/medialink'>,
862
- Parser<HTMLElement, Context, [
863
+ Parser<HTMLAnchorElement, Context, [
863
864
  Parser<HTMLElement[], Context, [
864
865
  MediaParser,
865
866
  ShortmediaParser,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.258.9",
3
+ "version": "0.259.2",
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",
@@ -4,14 +4,14 @@ import { inspect } from '../../../debug.test';
4
4
  describe('Unit: combinator/block', () => {
5
5
  describe('block', () => {
6
6
  it('invalid', () => {
7
- assert.throws(() => block(_ => [[], '\n'])(' \n'));
7
+ assert.throws(() => block(_ => [[], '\n'])({ source: ' \n', context: {} }));
8
8
  });
9
9
 
10
10
  it('valid', () => {
11
- assert.deepStrictEqual(inspect(block(_ => [[], ''])('\n')), [[], '']);
12
- assert.deepStrictEqual(inspect(block(_ => [[], ''])(' \n')), [[], '']);
13
- assert.deepStrictEqual(inspect(block(_ => [[], ''])('\n\n')), [[], '']);
14
- assert.deepStrictEqual(inspect(block(_ => [[], '\n'])('\n\n')), [[], '\n']);
11
+ assert.deepStrictEqual(inspect(block(_ => [[], ''])({ source: '\n', context: {} })), [[], '']);
12
+ assert.deepStrictEqual(inspect(block(_ => [[], ''])({ source: ' \n', context: {} })), [[], '']);
13
+ assert.deepStrictEqual(inspect(block(_ => [[], ''])({ source: '\n\n', context: {} })), [[], '']);
14
+ assert.deepStrictEqual(inspect(block(_ => [[], '\n'])({ source: '\n\n', context: {} })), [[], '\n']);
15
15
  });
16
16
 
17
17
  });
@@ -6,10 +6,11 @@ import { firstline, isEmpty } from './line';
6
6
  export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
7
7
  export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
8
8
  assert(parser);
9
- return (source, context = {}) => {
9
+ return input => {
10
+ const { source, context } = input;
10
11
  if (source === '') return;
11
12
  context.memo ??= new Memo();
12
- const result = parser(source, context);
13
+ const result = parser(input);
13
14
  if (!result) return;
14
15
  const rest = exec(result);
15
16
  if (separation && !isEmpty(firstline(rest))) return;
@@ -24,10 +24,11 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
24
24
  ? `|| source.slice(0, ${pattern.length}) === '${pattern}'`
25
25
  : `|| /${pattern.source}/${pattern.flags}.test(source)`),
26
26
  ].join(''))();
27
- return (source, context) => {
27
+ return input => {
28
+ const { source } = input;
28
29
  if (source === '') return;
29
30
  if (!match(source)) return;
30
- const result = parser!(source, context);
31
+ const result = parser!(input);
31
32
  assert(check(source, result));
32
33
  if (!result) return;
33
34
  assert(exec(result).length < source.length);
@@ -40,12 +41,13 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
40
41
  export function verify<P extends Parser<unknown>>(parser: P, cond: (results: readonly Tree<P>[], rest: string, context: Context<P>) => boolean): P;
41
42
  export function verify<T>(parser: Parser<T>, cond: (results: readonly T[], rest: string, context: Ctx) => boolean): Parser<T> {
42
43
  assert(parser);
43
- return (source, context) => {
44
+ return input => {
45
+ const { source } = input;
44
46
  if (source === '') return;
45
- const result = parser(source, context);
47
+ const result = parser(input);
46
48
  assert(check(source, result));
47
49
  if (!result) return;
48
- if (!cond(eval(result), exec(result), context)) return;
50
+ if (!cond(eval(result), exec(result), input.context)) return;
49
51
  assert(exec(result).length < source.length);
50
52
  return exec(result).length < source.length
51
53
  ? result
@@ -4,15 +4,15 @@ import { inspect } from '../../../debug.test';
4
4
  describe('Unit: combinator/line', () => {
5
5
  describe('line', () => {
6
6
  it('invalid', () => {
7
- assert.deepStrictEqual(inspect(line(_ => [[], ''])('')), undefined);
7
+ assert.deepStrictEqual(inspect(line(_ => [[], ''])({ source: '', context: {} })), undefined);
8
8
  });
9
9
 
10
10
  it('valid', () => {
11
- assert.deepStrictEqual(inspect(line(_ => [[], ''])(' ')), [[], '']);
12
- assert.deepStrictEqual(inspect(line(_ => [[], ''])('\n')), [[], '']);
13
- assert.deepStrictEqual(inspect(line(_ => [[], ''])('\n\n')), [[], '\n']);
14
- assert.deepStrictEqual(inspect(line(_ => [[], ''])(' \n')), [[], '']);
15
- assert.deepStrictEqual(inspect(line(_ => [[], '\n'])(' \n')), [[], '']);
11
+ assert.deepStrictEqual(inspect(line(_ => [[], ''])({ source: ' ', context: {} })), [[], '']);
12
+ assert.deepStrictEqual(inspect(line(_ => [[], ''])({ source: '\n', context: {} })), [[], '']);
13
+ assert.deepStrictEqual(inspect(line(_ => [[], ''])({ source: '\n\n', context: {} })), [[], '\n']);
14
+ assert.deepStrictEqual(inspect(line(_ => [[], ''])({ source: ' \n', context: {} })), [[], '']);
15
+ assert.deepStrictEqual(inspect(line(_ => [[], '\n'])({ source: ' \n', context: {} })), [[], '']);
16
16
  });
17
17
 
18
18
  });
@@ -5,15 +5,16 @@ import { Memo } from '../../data/parser/context/memo';
5
5
  export function line<P extends Parser<unknown>>(parser: P): P;
6
6
  export function line<T>(parser: Parser<T>): Parser<T> {
7
7
  assert(parser);
8
- return (source, context = {}) => {
8
+ return input => {
9
+ const { source, context } = input;
9
10
  if (source === '') return;
10
11
  context.memo ??= new Memo();
11
12
  const line = firstline(source);
12
- const memo = context.memo!;
13
- memo.offset += source.length - line.length;
14
- const result = parser(line, context);
13
+ context.offset ??= 0;
14
+ context.offset += source.length - line.length;
15
+ const result = parser({ source: line, context });
15
16
  assert(check(line, result));
16
- memo.offset -= source.length - line.length;
17
+ context.offset -= source.length - line.length;
17
18
  if (!result) return;
18
19
  return isEmpty(exec(result))
19
20
  ? [eval(result), source.slice(line.length)]
@@ -3,15 +3,16 @@ import { Parser, check } from '../../data/parser';
3
3
  export function convert<P extends Parser<unknown>>(conv: (source: string) => string, parser: P): P;
4
4
  export function convert<T>(conv: (source: string) => string, parser: Parser<T>): Parser<T> {
5
5
  assert(parser);
6
- return (source, context = {}) => {
6
+ return input => {
7
+ const { source, context } = input;
7
8
  if (source === '') return;
8
9
  const src = conv(source);
9
10
  if (src === '') return [[], ''];
10
- const memo = context.memo;
11
- memo && (memo.offset += source.length - src.length);
12
- const result = parser(src, context);
11
+ context.offset ??= 0;
12
+ context.offset += source.length - src.length;
13
+ const result = parser({ source: src, context });
13
14
  assert(check(src, result));
14
- memo && (memo.offset -= source.length - src.length);
15
+ context.offset -= source.length - src.length;
15
16
  return result;
16
17
  };
17
18
  }
@@ -3,7 +3,8 @@ import { firstline, isEmpty } from '../constraint/line';
3
3
  import { unshift } from 'spica/array';
4
4
 
5
5
  export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
6
- return source => {
6
+ return input => {
7
+ const { source } = input;
7
8
  if (source === '') return;
8
9
  const matches = source.match(opener);
9
10
  if (!matches) return;
@@ -4,20 +4,20 @@ import { inspect } from '../../../debug.test';
4
4
  describe('Unit: combinator/indent', () => {
5
5
  describe('indent', () => {
6
6
  it('valid', () => {
7
- const parser = indent((s, _) => [[s], '']);
8
- assert.deepStrictEqual(inspect(parser('', {})), undefined);
9
- assert.deepStrictEqual(inspect(parser(' ', {})), undefined);
10
- assert.deepStrictEqual(inspect(parser(' ', {})), undefined);
11
- assert.deepStrictEqual(inspect(parser('a ', {})), undefined);
12
- assert.deepStrictEqual(inspect(parser(' a\n', {})), [['a'], '']);
13
- assert.deepStrictEqual(inspect(parser(' a ', {})), [['a '], '']);
14
- assert.deepStrictEqual(inspect(parser(' a \n', {})), [['a '], '']);
15
- assert.deepStrictEqual(inspect(parser(' a', {})), [['a'], '']);
16
- assert.deepStrictEqual(inspect(parser(' a\n a', {})), [['a\na'], '']);
17
- assert.deepStrictEqual(inspect(parser(' a\n a', {})), [['a\n a'], '']);
18
- assert.deepStrictEqual(inspect(parser(' a\n a', {})), [['a'], ' a']);
19
- assert.deepStrictEqual(inspect(parser(' \ta', {})), [['\ta'], '']);
20
- assert.deepStrictEqual(inspect(parser('\ta', {})), [['a'], '']);
7
+ const parser = indent(({ source }) => [[source], '']);
8
+ assert.deepStrictEqual(inspect(parser({ source: '', context: {} })), undefined);
9
+ assert.deepStrictEqual(inspect(parser({ source: ' ', context: {} })), undefined);
10
+ assert.deepStrictEqual(inspect(parser({ source: ' ', context: {} })), undefined);
11
+ assert.deepStrictEqual(inspect(parser({ source: 'a ', context: {} })), undefined);
12
+ assert.deepStrictEqual(inspect(parser({ source: ' a\n', context: {} })), [['a'], '']);
13
+ assert.deepStrictEqual(inspect(parser({ source: ' a ', context: {} })), [['a '], '']);
14
+ assert.deepStrictEqual(inspect(parser({ source: ' a \n', context: {} })), [['a '], '']);
15
+ assert.deepStrictEqual(inspect(parser({ source: ' a', context: {} })), [['a'], '']);
16
+ assert.deepStrictEqual(inspect(parser({ source: ' a\n a', context: {} })), [['a\na'], '']);
17
+ assert.deepStrictEqual(inspect(parser({ source: ' a\n a', context: {} })), [['a\n a'], '']);
18
+ assert.deepStrictEqual(inspect(parser({ source: ' a\n a', context: {} })), [['a'], ' a']);
19
+ assert.deepStrictEqual(inspect(parser({ source: ' \ta', context: {} })), [['\ta'], '']);
20
+ assert.deepStrictEqual(inspect(parser({ source: '\ta', context: {} })), [['a'], '']);
21
21
  });
22
22
 
23
23
  });
@@ -17,14 +17,14 @@ export function indent<T>(opener: RegExp | Parser<T>, parser?: Parser<T> | boole
17
17
  opener,
18
18
  memoize(
19
19
  ([indent]) =>
20
- some(line(open(indent, source => [[source], '']))),
20
+ some(line(open(indent, ({ source }) => [[source], '']))),
21
21
  ([indent]) => indent.length * 2 + +(indent[0] === ' '), [])), separation),
22
22
  (lines, rest, context) => {
23
23
  assert(parser = parser as Parser<T>);
24
- const memo = context.memo;
25
- memo && (memo.offset += rest.length);
26
- const result = parser(trimBlockEnd(lines.join('')), context);
27
- memo && (memo.offset -= rest.length);
24
+ context.offset ??= 0;
25
+ context.offset += rest.length;
26
+ const result = parser({ source: trimBlockEnd(lines.join('')), context });
27
+ context.offset -= rest.length;
28
28
  return result && exec(result) === ''
29
29
  ? [eval(result), rest]
30
30
  : undefined;
@@ -3,6 +3,6 @@ import { Parser } from '../../data/parser';
3
3
  export function lazy<P extends Parser<unknown>>(builder: () => P): P;
4
4
  export function lazy<T>(builder: () => Parser<T>): Parser<T> {
5
5
  let parser: Parser<T>;
6
- return (source, context) =>
7
- (parser ??= builder())(source, context);
6
+ return input =>
7
+ (parser ??= builder())(input);
8
8
  }
@@ -4,12 +4,13 @@ import { Parser, exec, check } from '../../data/parser';
4
4
  export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
5
5
  export function match<T>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<T>): Parser<T> {
6
6
  assert(!pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^'));
7
- return (source, context) => {
7
+ return input => {
8
+ const { source } = 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]));
12
- const result = f(param)(source, context);
13
+ const result = f(param)(input);
13
14
  assert(check(source, result, false));
14
15
  if (!result) return;
15
16
  return exec(result).length < source.length && exec(result).length <= source.length
@@ -1,14 +1,14 @@
1
- import { Parser, Result, Ctx, Tree, Context } from '../../data/parser';
1
+ import { Parser, Input, Result, Tree, Context } from '../../data/parser';
2
2
 
3
- export function recover<P extends Parser<unknown>>(parser: P, fallback: (source: string, context: Context<P>, reason: unknown) => Result<Tree<P>>): P;
4
- export function recover<T>(parser: Parser<T>, fallback: (source: string, context: Ctx, reason: unknown) => Result<T>): Parser<T> {
5
- return (source, context) => {
3
+ export function recover<P extends Parser<unknown>>(parser: P, fallback: (input: Input<Context<P>>, reason: unknown) => Result<Tree<P>>): P;
4
+ export function recover<T>(parser: Parser<T>, fallback: (input: Input, reason: unknown) => Result<T>): Parser<T> {
5
+ return input => {
6
6
  try {
7
- return parser(source, context);
7
+ return parser(input);
8
8
  }
9
9
  catch (reason) {
10
10
  assert(reason instanceof Error && reason.name === 'AssertionError' && !+console.error(reason) && eval(`throw new Error("${reason.name}")`) || 1);
11
- return fallback(source, context, reason);
11
+ return fallback(input, reason);
12
12
  }
13
13
  };
14
14
  }
@@ -8,16 +8,17 @@ export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
8
8
  const match: (source: string) => string = typeof scope === 'string'
9
9
  ? source => source.slice(0, scope.length) === scope ? scope : ''
10
10
  : source => source.match(scope)?.[0] ?? '';
11
- return (source, context = {}) => {
11
+ return input => {
12
+ const { source, context } = input;
12
13
  if (source === '') return;
13
14
  const src = match(source);
14
15
  assert(source.startsWith(src));
15
16
  if (src === '') return;
16
- const memo = context.memo;
17
- memo && (memo.offset += source.length - src.length);
18
- const result = parser(src, context);
17
+ context.offset ??= 0;
18
+ context.offset += source.length - src.length;
19
+ const result = parser({ source: src, context });
19
20
  assert(check(src, result));
20
- memo && (memo.offset -= source.length - src.length);
21
+ context.offset -= source.length - src.length;
21
22
  if (!result) return;
22
23
  assert(exec(result).length < src.length);
23
24
  return exec(result).length < src.length
@@ -31,21 +32,23 @@ export function rewrite<P extends Parser<unknown>>(scope: Parser<unknown, Contex
31
32
  export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T> {
32
33
  assert(scope);
33
34
  assert(parser);
34
- return (source, context = {}) => {
35
+ return input => {
36
+ const { source, context } = input;
35
37
  if (source === '') return;
36
38
  const memo = context.memo;
37
39
  context.memo = undefined;
38
- const res1 = scope(source, context);
40
+ const res1 = scope(input);
39
41
  assert(check(source, res1));
40
42
  context.memo = memo;
41
43
  if (!res1 || exec(res1).length >= source.length) return;
42
44
  const src = source.slice(0, source.length - exec(res1).length);
43
45
  assert(src !== '');
44
46
  assert(source.startsWith(src));
45
- memo && (memo.offset += source.length - src.length);
46
- const res2 = parser(src, context);
47
+ context.offset ??= 0;
48
+ context.offset += source.length - src.length;
49
+ const res2 = parser({ source: src, context });
47
50
  assert(check(src, res2));
48
- memo && (memo.offset -= source.length - src.length);
51
+ context.offset -= source.length - src.length;
49
52
  if (!res2) return;
50
53
  assert(exec(res2) === '');
51
54
  return exec(res2).length < src.length
@@ -1,5 +1,5 @@
1
1
  import { undefined } from 'spica/global';
2
- import { Parser, Result, Ctx, Tree, Context, SubParsers, SubTree, IntermediateParser, eval, exec, check } from '../../data/parser';
2
+ import { Parser, Input, Result, Ctx, Tree, Context, SubParsers, SubTree, IntermediateParser, eval, exec, check } from '../../data/parser';
3
3
  import { fmap } from '../monad/fmap';
4
4
  import { unshift, push } from 'spica/array';
5
5
 
@@ -38,19 +38,20 @@ export function surround<T>(
38
38
  case 'object':
39
39
  return surround(opener, parser, match(closer), optional, f, g);
40
40
  }
41
- return (lmr_, context) => {
41
+ return input => {
42
+ const { source: lmr_, context } = input;
42
43
  if (lmr_ === '') return;
43
- const res1 = opener(lmr_, context);
44
+ const res1 = opener({ source: lmr_, context });
44
45
  assert(check(lmr_, res1, false));
45
46
  if (!res1) return;
46
47
  const rl = eval(res1);
47
48
  const mr_ = exec(res1);
48
- const res2 = mr_ !== '' ? parser(mr_, context) : undefined;
49
+ const res2 = mr_ !== '' ? parser({ source: mr_, context }) : undefined;
49
50
  assert(check(mr_, res2));
50
51
  const rm = eval(res2);
51
52
  const r_ = exec(res2, mr_);
52
53
  if (!rm && !optional) return;
53
- const res3 = closer(r_, context);
54
+ const res3 = closer({ source: r_, context });
54
55
  assert(check(r_, res3, false));
55
56
  const rr = eval(res3);
56
57
  const rest = exec(res3, r_);
@@ -65,12 +66,12 @@ export function surround<T>(
65
66
  };
66
67
  }
67
68
 
68
- function match(pattern: string | RegExp): (source: string, context: Ctx) => [never[], string] | undefined {
69
+ function match(pattern: string | RegExp): (input: Input) => [never[], string] | undefined {
69
70
  switch (typeof pattern) {
70
71
  case 'string':
71
- return source => source.slice(0, pattern.length) === pattern ? [[], source.slice(pattern.length)] : undefined;
72
+ return ({ source }) => source.slice(0, pattern.length) === pattern ? [[], source.slice(pattern.length)] : undefined;
72
73
  case 'object':
73
- return source => {
74
+ return ({ source }) => {
74
75
  const m = source.match(pattern);
75
76
  return m
76
77
  ? [[], source.slice(m[0].length)]
@@ -4,15 +4,15 @@ import { inspect } from '../../../debug.test';
4
4
  describe('Unit: combinator/trim', () => {
5
5
  describe('trim', () => {
6
6
  it('', () => {
7
- const parser = trim(s => [[s], '']);
8
- assert.deepStrictEqual(inspect(parser('')), undefined);
9
- assert.deepStrictEqual(inspect(parser('a')), [['a'], '']);
10
- assert.deepStrictEqual(inspect(parser('a\n')), [['a'], '']);
11
- assert.deepStrictEqual(inspect(parser('a ')), [['a'], '']);
12
- assert.deepStrictEqual(inspect(parser('a \n')), [['a'], '']);
13
- assert.deepStrictEqual(inspect(parser(' a')), [['a'], '']);
14
- assert.deepStrictEqual(inspect(parser(' a ')), [['a'], '']);
15
- assert.deepStrictEqual(inspect(parser(' a \n b \n')), [['a \n b'], '']);
7
+ const parser = trim(({ source }) => [[source], '']);
8
+ assert.deepStrictEqual(inspect(parser({ source: '', context: {} })), undefined);
9
+ assert.deepStrictEqual(inspect(parser({ source: 'a', context: {} })), [['a'], '']);
10
+ assert.deepStrictEqual(inspect(parser({ source: 'a\n', context: {} })), [['a'], '']);
11
+ assert.deepStrictEqual(inspect(parser({ source: 'a ', context: {} })), [['a'], '']);
12
+ assert.deepStrictEqual(inspect(parser({ source: 'a \n', context: {} })), [['a'], '']);
13
+ assert.deepStrictEqual(inspect(parser({ source: ' a', context: {} })), [['a'], '']);
14
+ assert.deepStrictEqual(inspect(parser({ source: ' a ', context: {} })), [['a'], '']);
15
+ assert.deepStrictEqual(inspect(parser({ source: ' a \n b \n', context: {} })), [['a \n b'], '']);
16
16
  });
17
17
 
18
18
  });
@@ -7,9 +7,10 @@ export function bind<T, P extends Parser<unknown>>(parser: Parser<T, Context<P>,
7
7
  export function bind<U, P extends Parser<unknown>>(parser: P, f: (nodes: Tree<P>[], rest: string, context: Context<P>) => Result<U, Context<P>, SubParsers<P>>): Parser<U, Context<P>, SubParsers<P>>;
8
8
  export function bind<T, U>(parser: Parser<T>, f: (nodes: T[], rest: string, context: Ctx) => Result<U>): Parser<U> {
9
9
  assert(parser);
10
- return (source, context) => {
10
+ return input => {
11
+ const { source, context } = input;
11
12
  if (source === '') return;
12
- const res1 = parser(source, context);
13
+ const res1 = parser(input);
13
14
  assert(check(source, res1));
14
15
  if (!res1) return;
15
16
  const res2 = f(eval(res1), exec(res1), context);
@@ -3,14 +3,13 @@ export class Memo {
3
3
  public get length(): number {
4
4
  return this.memory.length;
5
5
  }
6
- public offset = 0;
7
6
  public get(
8
7
  position: number,
9
8
  syntax: number,
10
9
  state: number,
11
10
  ): readonly [any[], number] | readonly [] | undefined {
12
11
  //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
13
- const cache = this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
12
+ const cache = this.memory[position - 1]?.[`${syntax}:${state}`];
14
13
  return cache?.length === 2
15
14
  ? [cache[0].slice(), cache[1]]
16
15
  : cache;
@@ -22,7 +21,7 @@ export class Memo {
22
21
  nodes: any[] | undefined,
23
22
  offset: number,
24
23
  ): void {
25
- const record = this.memory[position + this.offset - 1] ??= {};
24
+ const record = this.memory[position - 1] ??= {};
26
25
  assert(!record[`${syntax}:${state}`]);
27
26
  record[`${syntax}:${state}`] = nodes
28
27
  ? [nodes.slice(), offset]
@@ -31,7 +30,7 @@ export class Memo {
31
30
  }
32
31
  public clear(position: number): void {
33
32
  const memory = this.memory;
34
- for (let i = position + this.offset, len = memory.length; i < len; ++i) {
33
+ for (let i = position, len = memory.length; i < len; ++i) {
35
34
  memory.pop();
36
35
  }
37
36
  //console.log('clear', position + this.offset + 1);
@@ -10,26 +10,26 @@ describe('Unit: combinator/data/parser/context', () => {
10
10
 
11
11
  describe('reset', () => {
12
12
  const parser: Parser<number> = some(creation(
13
- (s, context) => [[context.resources?.clock ?? NaN], s.slice(1)]));
13
+ ({ source, context }) => [[context.resources?.clock ?? NaN], source.slice(1)]));
14
14
 
15
15
  it('root', () => {
16
16
  const base: Context = { resources: { clock: 3, recursion: 1 } };
17
17
  const ctx: Context = {};
18
- assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
18
+ assert.deepStrictEqual(reset(base, parser)({ source: '123', context: ctx }), [[3, 2, 1], '']);
19
19
  assert(base.resources?.clock === 3);
20
20
  assert(ctx.resources?.clock === undefined);
21
- assert.throws(() => reset(base, parser)('1234', ctx));
21
+ assert.throws(() => reset(base, parser)({ source: '1234', context: ctx }));
22
22
  assert(ctx.resources?.clock === undefined);
23
- assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
23
+ assert.deepStrictEqual(reset(base, parser)({ source: '123', context: ctx }), [[3, 2, 1], '']);
24
24
  });
25
25
 
26
26
  it('node', () => {
27
27
  const base: Context = { resources: { clock: 3, recursion: 1 } };
28
28
  const ctx: Context = { resources: { clock: 1, recursion: 1 } };
29
- assert.deepStrictEqual(reset(base, parser)('1', ctx), [[1], '']);
29
+ assert.deepStrictEqual(reset(base, parser)({ source: '1', context: ctx }), [[1], '']);
30
30
  assert(base.resources?.clock === 3);
31
31
  assert(ctx.resources?.clock === 0);
32
- assert.throws(() => reset(base, parser)('1', ctx));
32
+ assert.throws(() => reset(base, parser)({ source: '1', context: ctx }));
33
33
  assert(ctx.resources?.clock === 0);
34
34
  });
35
35
 
@@ -37,15 +37,15 @@ describe('Unit: combinator/data/parser/context', () => {
37
37
 
38
38
  describe('context', () => {
39
39
  const parser: Parser<boolean, Context> = some(creation(
40
- (s, context) => [[context.status!], s.slice(1)]));
40
+ ({ source, context }) => [[context.status!], source.slice(1)]));
41
41
 
42
42
  it('', () => {
43
43
  const base: Context = { status: true };
44
44
  const ctx: Context = { resources: { clock: 3, recursion: 1 } };
45
- assert.deepStrictEqual(context(base, parser)('123', ctx), [[true, true, true], '']);
45
+ assert.deepStrictEqual(context(base, parser)({ source: '123', context: ctx }), [[true, true, true], '']);
46
46
  assert(ctx.resources?.clock === 0);
47
47
  assert(ctx.status === undefined);
48
- assert.throws(() => reset(base, parser)('1', ctx));
48
+ assert.throws(() => reset(base, parser)({ source: '1', context: ctx }));
49
49
  assert(ctx.resources?.clock === 0);
50
50
  assert(ctx.status === undefined);
51
51
  });