securemark 0.259.1 → 0.260.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/design.md +16 -5
  3. package/dist/index.js +548 -226
  4. package/package.json +1 -1
  5. package/src/combinator/control/constraint/block.test.ts +5 -5
  6. package/src/combinator/control/constraint/block.ts +2 -2
  7. package/src/combinator/control/constraint/contract.ts +4 -4
  8. package/src/combinator/control/constraint/line.test.ts +6 -6
  9. package/src/combinator/control/constraint/line.ts +5 -5
  10. package/src/combinator/control/manipulation/convert.ts +5 -5
  11. package/src/combinator/control/manipulation/fence.ts +1 -1
  12. package/src/combinator/control/manipulation/indent.test.ts +14 -14
  13. package/src/combinator/control/manipulation/indent.ts +5 -5
  14. package/src/combinator/control/manipulation/lazy.ts +2 -2
  15. package/src/combinator/control/manipulation/match.ts +2 -2
  16. package/src/combinator/control/manipulation/recovery.ts +6 -6
  17. package/src/combinator/control/manipulation/scope.ts +11 -10
  18. package/src/combinator/control/manipulation/surround.ts +9 -8
  19. package/src/combinator/control/manipulation/trim.test.ts +9 -9
  20. package/src/combinator/control/monad/bind.ts +2 -2
  21. package/src/combinator/data/parser/context/memo.ts +3 -4
  22. package/src/combinator/data/parser/context.test.ts +9 -9
  23. package/src/combinator/data/parser/context.ts +18 -17
  24. package/src/combinator/data/parser/inits.ts +2 -2
  25. package/src/combinator/data/parser/sequence.test.ts +10 -10
  26. package/src/combinator/data/parser/sequence.ts +2 -2
  27. package/src/combinator/data/parser/some.test.ts +13 -13
  28. package/src/combinator/data/parser/some.ts +2 -2
  29. package/src/combinator/data/parser/subsequence.test.ts +14 -14
  30. package/src/combinator/data/parser/union.test.ts +10 -10
  31. package/src/combinator/data/parser/union.ts +2 -2
  32. package/src/combinator/data/parser.ts +6 -1
  33. package/src/parser/api/bind.ts +1 -1
  34. package/src/parser/api/header.ts +1 -1
  35. package/src/parser/api/normalize.ts +1 -1
  36. package/src/parser/api/parse.test.ts +19 -19
  37. package/src/parser/api/parse.ts +1 -1
  38. package/src/parser/autolink.test.ts +7 -7
  39. package/src/parser/autolink.ts +2 -2
  40. package/src/parser/block/blockquote.test.ts +9 -9
  41. package/src/parser/block/blockquote.ts +1 -1
  42. package/src/parser/block/codeblock.test.ts +5 -5
  43. package/src/parser/block/codeblock.ts +1 -1
  44. package/src/parser/block/dlist.test.ts +2 -2
  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 +2 -2
  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 +8 -8
  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 +2 -2
  64. package/src/parser/block/olist.ts +5 -5
  65. package/src/parser/block/paragraph.test.ts +15 -15
  66. package/src/parser/block/reply/cite.test.ts +12 -12
  67. package/src/parser/block/reply/cite.ts +2 -2
  68. package/src/parser/block/reply/quote.test.ts +4 -4
  69. package/src/parser/block/reply/quote.ts +2 -2
  70. package/src/parser/block/reply.test.ts +9 -9
  71. package/src/parser/block/sidefence.test.ts +7 -7
  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 +2 -2
  75. package/src/parser/block/ulist.ts +1 -1
  76. package/src/parser/block.ts +2 -2
  77. package/src/parser/context.ts +8 -7
  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 +4 -4
  81. package/src/parser/inline/autolink/account.test.ts +12 -12
  82. package/src/parser/inline/autolink/account.ts +4 -2
  83. package/src/parser/inline/autolink/anchor.test.ts +10 -10
  84. package/src/parser/inline/autolink/anchor.ts +14 -11
  85. package/src/parser/inline/autolink/channel.test.ts +4 -4
  86. package/src/parser/inline/autolink/email.test.ts +1 -1
  87. package/src/parser/inline/autolink/email.ts +1 -1
  88. package/src/parser/inline/autolink/hashnum.test.ts +1 -1
  89. package/src/parser/inline/autolink/hashnum.ts +4 -2
  90. package/src/parser/inline/autolink/hashtag.test.ts +21 -21
  91. package/src/parser/inline/autolink/hashtag.ts +4 -2
  92. package/src/parser/inline/autolink/url.test.ts +56 -56
  93. package/src/parser/inline/bracket.test.ts +1 -1
  94. package/src/parser/inline/code.test.ts +1 -1
  95. package/src/parser/inline/code.ts +2 -2
  96. package/src/parser/inline/comment.test.ts +1 -1
  97. package/src/parser/inline/deletion.test.ts +1 -1
  98. package/src/parser/inline/emphasis.test.ts +1 -1
  99. package/src/parser/inline/emstrong.ts +2 -2
  100. package/src/parser/inline/escape.ts +3 -3
  101. package/src/parser/inline/extension/index.test.ts +1 -1
  102. package/src/parser/inline/extension/indexer.test.ts +1 -1
  103. package/src/parser/inline/extension/label.test.ts +1 -1
  104. package/src/parser/inline/extension/placeholder.test.ts +1 -1
  105. package/src/parser/inline/html.test.ts +1 -3
  106. package/src/parser/inline/html.ts +1 -1
  107. package/src/parser/inline/htmlentity.test.ts +1 -1
  108. package/src/parser/inline/htmlentity.ts +1 -1
  109. package/src/parser/inline/insertion.test.ts +1 -1
  110. package/src/parser/inline/link.test.ts +111 -111
  111. package/src/parser/inline/link.ts +30 -28
  112. package/src/parser/inline/mark.test.ts +1 -1
  113. package/src/parser/inline/math.test.ts +1 -1
  114. package/src/parser/inline/math.ts +1 -1
  115. package/src/parser/inline/media.test.ts +2 -1
  116. package/src/parser/inline/media.ts +3 -3
  117. package/src/parser/inline/reference.test.ts +4 -4
  118. package/src/parser/inline/ruby.test.ts +1 -1
  119. package/src/parser/inline/ruby.ts +3 -3
  120. package/src/parser/inline/shortmedia.test.ts +1 -1
  121. package/src/parser/inline/strong.test.ts +1 -1
  122. package/src/parser/inline/template.test.ts +1 -1
  123. package/src/parser/inline.test.ts +52 -52
  124. package/src/parser/locale.test.ts +1 -1
  125. package/src/parser/segment.ts +1 -1
  126. package/src/parser/source/escapable.test.ts +1 -1
  127. package/src/parser/source/escapable.ts +1 -1
  128. package/src/parser/source/line.test.ts +1 -1
  129. package/src/parser/source/line.ts +2 -2
  130. package/src/parser/source/str.ts +4 -4
  131. package/src/parser/source/text.test.ts +1 -1
  132. package/src/parser/source/text.ts +3 -3
  133. package/src/parser/source/unescapable.test.ts +1 -1
  134. package/src/parser/source/unescapable.ts +1 -1
  135. package/src/parser/visibility.ts +13 -13
  136. package/src/renderer/render/media/pdf.ts +1 -0
  137. package/src/renderer/render/media/twitter.ts +7 -1
  138. package/src/util/info.ts +2 -4
  139. package/src/util/quote.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.259.1",
3
+ "version": "0.260.1",
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,10 @@ 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 ({ source, context }) => {
10
10
  if (source === '') return;
11
11
  context.memo ??= new Memo();
12
- const result = parser(source, context);
12
+ const result = parser({ source, context });
13
13
  if (!result) return;
14
14
  const rest = exec(result);
15
15
  if (separation && !isEmpty(firstline(rest))) return;
@@ -24,10 +24,10 @@ 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 ({ source, context }) => {
28
28
  if (source === '') return;
29
29
  if (!match(source)) return;
30
- const result = parser!(source, context);
30
+ const result = parser!({ source, context });
31
31
  assert(check(source, result));
32
32
  if (!result) return;
33
33
  assert(exec(result).length < source.length);
@@ -40,9 +40,9 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
40
40
  export function verify<P extends Parser<unknown>>(parser: P, cond: (results: readonly Tree<P>[], rest: string, context: Context<P>) => boolean): P;
41
41
  export function verify<T>(parser: Parser<T>, cond: (results: readonly T[], rest: string, context: Ctx) => boolean): Parser<T> {
42
42
  assert(parser);
43
- return (source, context) => {
43
+ return ({ source, context }) => {
44
44
  if (source === '') return;
45
- const result = parser(source, context);
45
+ const result = parser({ source, context });
46
46
  assert(check(source, result));
47
47
  if (!result) return;
48
48
  if (!cond(eval(result), exec(result), context)) return;
@@ -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,15 @@ 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 ({ source, context }) => {
9
9
  if (source === '') return;
10
10
  context.memo ??= new Memo();
11
11
  const line = firstline(source);
12
- const memo = context.memo!;
13
- memo.offset += source.length - line.length;
14
- const result = parser(line, context);
12
+ context.offset ??= 0;
13
+ context.offset += source.length - line.length;
14
+ const result = parser({ source: line, context });
15
15
  assert(check(line, result));
16
- memo.offset -= source.length - line.length;
16
+ context.offset -= source.length - line.length;
17
17
  if (!result) return;
18
18
  return isEmpty(exec(result))
19
19
  ? [eval(result), source.slice(line.length)]
@@ -3,15 +3,15 @@ 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 ({ source, context }) => {
7
7
  if (source === '') return;
8
8
  const src = conv(source);
9
9
  if (src === '') return [[], ''];
10
- const memo = context.memo;
11
- memo && (memo.offset += source.length - src.length);
12
- const result = parser(src, context);
10
+ context.offset ??= 0;
11
+ context.offset += source.length - src.length;
12
+ const result = parser({ source: src, context });
13
13
  assert(check(src, result));
14
- memo && (memo.offset -= source.length - src.length);
14
+ context.offset -= source.length - src.length;
15
15
  return result;
16
16
  };
17
17
  }
@@ -3,7 +3,7 @@ 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 ({ source }) => {
7
7
  if (source === '') return;
8
8
  const matches = source.match(opener);
9
9
  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,12 @@ 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 ({ source, context }) => {
8
8
  if (source === '') return;
9
9
  const param = source.match(pattern);
10
10
  if (!param) return;
11
11
  assert(source.startsWith(param[0]));
12
- const result = f(param)(source, context);
12
+ const result = f(param)({ source, context });
13
13
  assert(check(source, result, false));
14
14
  if (!result) return;
15
15
  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,16 @@ 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 ({ source, context }) => {
12
12
  if (source === '') return;
13
13
  const src = match(source);
14
14
  assert(source.startsWith(src));
15
15
  if (src === '') return;
16
- const memo = context.memo;
17
- memo && (memo.offset += source.length - src.length);
18
- const result = parser(src, context);
16
+ context.offset ??= 0;
17
+ context.offset += source.length - src.length;
18
+ const result = parser({ source: src, context });
19
19
  assert(check(src, result));
20
- memo && (memo.offset -= source.length - src.length);
20
+ context.offset -= source.length - src.length;
21
21
  if (!result) return;
22
22
  assert(exec(result).length < src.length);
23
23
  return exec(result).length < src.length
@@ -31,21 +31,22 @@ export function rewrite<P extends Parser<unknown>>(scope: Parser<unknown, Contex
31
31
  export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T> {
32
32
  assert(scope);
33
33
  assert(parser);
34
- return (source, context = {}) => {
34
+ return ({ source, context }) => {
35
35
  if (source === '') return;
36
36
  const memo = context.memo;
37
37
  context.memo = undefined;
38
- const res1 = scope(source, context);
38
+ const res1 = scope({ source, context });
39
39
  assert(check(source, res1));
40
40
  context.memo = memo;
41
41
  if (!res1 || exec(res1).length >= source.length) return;
42
42
  const src = source.slice(0, source.length - exec(res1).length);
43
43
  assert(src !== '');
44
44
  assert(source.startsWith(src));
45
- memo && (memo.offset += source.length - src.length);
46
- const res2 = parser(src, context);
45
+ context.offset ??= 0;
46
+ context.offset += source.length - src.length;
47
+ const res2 = parser({ source: src, context });
47
48
  assert(check(src, res2));
48
- memo && (memo.offset -= source.length - src.length);
49
+ context.offset -= source.length - src.length;
49
50
  if (!res2) return;
50
51
  assert(exec(res2) === '');
51
52
  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 ({ source, context }) => {
42
+ const lmr_ = source;
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,9 @@ 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 ({ source, context }) => {
11
11
  if (source === '') return;
12
- const res1 = parser(source, context);
12
+ const res1 = parser({ source, context });
13
13
  assert(check(source, res1));
14
14
  if (!res1) return;
15
15
  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
  });
@@ -9,7 +9,7 @@ export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
9
9
  assert(Object.freeze(base));
10
10
  const changes = Object.entries(base);
11
11
  const values = Array(changes.length);
12
- return (source, context) =>
12
+ return ({ source, context }) =>
13
13
  apply(parser, source, ObjectCreate(context), changes, values);
14
14
  }
15
15
 
@@ -19,7 +19,7 @@ export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
19
19
  assert(Object.freeze(base));
20
20
  const changes = Object.entries(base);
21
21
  const values = Array(changes.length);
22
- return (source, context) =>
22
+ return ({ source, context }) =>
23
23
  apply(parser, source, context, changes, values);
24
24
  }
25
25
 
@@ -41,7 +41,7 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
41
41
  context[prop] = change[1];
42
42
  }
43
43
  }
44
- const result = parser(source, context);
44
+ const result = parser({ source, context });
45
45
  if (context) for (let i = 0; i < changes.length; ++i) {
46
46
  const change = changes[i];
47
47
  const prop = change[0];
@@ -58,11 +58,12 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
58
58
 
59
59
  export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, state: number, parser: P): P;
60
60
  export function syntax<T>(syntax: number, prec: number, cost: number, state: number, parser?: Parser<T>): Parser<T> {
61
- return creation(cost, precedence(prec, (source, context) => {
61
+ return creation(cost, precedence(prec, ({ source, context }) => {
62
62
  if (source === '') return;
63
63
  const memo = context.memo ??= new Memo();
64
64
  context.memorable ??= ~0;
65
- const position = source.length;
65
+ context.offset ??= 0;
66
+ const position = source.length + context.offset!;
66
67
  const st0 = context.state ?? 0;
67
68
  const st1 = context.state = st0 | state;
68
69
  const cache = syntax && memo.get(position, syntax, st1);
@@ -70,7 +71,7 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
70
71
  ? cache.length === 0
71
72
  ? undefined
72
73
  : [cache[0], source.slice(cache[1])]
73
- : parser!(source, context);
74
+ : parser!({ source, context });
74
75
  if (syntax && st0 & context.memorable!) {
75
76
  cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
76
77
  assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
@@ -89,12 +90,12 @@ export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
89
90
  export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
90
91
  if (typeof cost === 'function') return creation(1, cost);
91
92
  assert(cost > 0);
92
- return (source, context) => {
93
- const { resources = { clock: 1, recursion: 1 } } = context;
93
+ return ({ source, context }) => {
94
+ const resources = context.resources ?? { clock: 1, recursion: 1 };
94
95
  if (resources.clock <= 0) throw new Error('Too many creations');
95
96
  if (resources.recursion <= 0) throw new Error('Too much recursion');
96
97
  --resources.recursion;
97
- const result = parser!(source, context);
98
+ const result = parser!({ source, context });
98
99
  ++resources.recursion;
99
100
  if (result) {
100
101
  resources.clock -= cost;
@@ -106,10 +107,10 @@ export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown
106
107
  export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
107
108
  export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
108
109
  assert(precedence > 0);
109
- return (source, context) => {
110
+ return ({ source, context }) => {
110
111
  const p = context.precedence;
111
112
  context.precedence = precedence;
112
- const result = parser(source, context);
113
+ const result = parser({ source, context });
113
114
  context.precedence = p;
114
115
  return result;
115
116
  };
@@ -117,9 +118,9 @@ export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T>
117
118
 
118
119
  export function guard<P extends Parser<unknown>>(f: (context: Context<P>) => boolean | number, parser: P): P;
119
120
  export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T>): Parser<T> {
120
- return (source, context) =>
121
+ return ({ source, context }) =>
121
122
  f(context)
122
- ? parser(source, context)
123
+ ? parser({ source, context })
123
124
  : undefined;
124
125
  }
125
126
 
@@ -131,12 +132,12 @@ export function constraint<T>(state: number, positive: boolean | Parser<T>, pars
131
132
  positive = true;
132
133
  }
133
134
  assert(state);
134
- return (source, context) => {
135
+ return ({ source, context }) => {
135
136
  const s = positive
136
137
  ? state & context.state!
137
138
  : state & ~context.state!;
138
139
  return s === state
139
- ? parser!(source, context)
140
+ ? parser!({ source, context })
140
141
  : undefined;
141
142
  };
142
143
  }
@@ -149,12 +150,12 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
149
150
  positive = true;
150
151
  }
151
152
  assert(state);
152
- return (source, context) => {
153
+ return ({ source, context }) => {
153
154
  const s = context.state ?? 0;
154
155
  context.state = positive
155
156
  ? s | state
156
157
  : s & ~state;
157
- const result = parser!(source, context);
158
+ const result = parser!({ source, context });
158
159
  context.state = s;
159
160
  return result;
160
161
  };
@@ -6,13 +6,13 @@ export function inits<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?
6
6
  export function inits<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
7
  assert(parsers.every(f => f));
8
8
  if (parsers.length === 1) return parsers[0];
9
- return (source, context) => {
9
+ return ({ source, context }) => {
10
10
  let rest = source;
11
11
  let nodes: T[] | undefined;
12
12
  for (let i = 0, len = parsers.length; i < len; ++i) {
13
13
  if (rest === '') break;
14
14
  if (context.delimiters?.match(rest, context.precedence)) break;
15
- const result = parsers[i](rest, context);
15
+ const result = parsers[i]({ source: rest, context });
16
16
  assert(check(rest, result));
17
17
  if (!result) break;
18
18
  nodes = nodes