securemark 0.259.1 → 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 (129) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/design.md +2 -1
  3. package/dist/index.js +427 -154
  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 +3 -2
  7. package/src/combinator/control/constraint/contract.ts +7 -5
  8. package/src/combinator/control/constraint/line.test.ts +6 -6
  9. package/src/combinator/control/constraint/line.ts +6 -5
  10. package/src/combinator/control/manipulation/convert.ts +6 -5
  11. package/src/combinator/control/manipulation/fence.ts +2 -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 +3 -2
  16. package/src/combinator/control/manipulation/recovery.ts +6 -6
  17. package/src/combinator/control/manipulation/scope.ts +13 -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 +3 -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 +23 -18
  24. package/src/combinator/data/parser/inits.ts +3 -2
  25. package/src/combinator/data/parser/sequence.test.ts +10 -10
  26. package/src/combinator/data/parser/sequence.ts +3 -2
  27. package/src/combinator/data/parser/some.test.ts +13 -13
  28. package/src/combinator/data/parser/some.ts +3 -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.ts +1 -1
  37. package/src/parser/autolink.test.ts +1 -1
  38. package/src/parser/autolink.ts +2 -2
  39. package/src/parser/block/blockquote.test.ts +1 -1
  40. package/src/parser/block/blockquote.ts +1 -1
  41. package/src/parser/block/codeblock.test.ts +1 -1
  42. package/src/parser/block/codeblock.ts +1 -1
  43. package/src/parser/block/dlist.test.ts +1 -1
  44. package/src/parser/block/extension/aside.test.ts +1 -1
  45. package/src/parser/block/extension/example.test.ts +1 -1
  46. package/src/parser/block/extension/example.ts +1 -1
  47. package/src/parser/block/extension/fig.test.ts +1 -1
  48. package/src/parser/block/extension/figbase.test.ts +1 -1
  49. package/src/parser/block/extension/figure.test.ts +1 -1
  50. package/src/parser/block/extension/figure.ts +1 -1
  51. package/src/parser/block/extension/message.test.ts +1 -1
  52. package/src/parser/block/extension/message.ts +1 -1
  53. package/src/parser/block/extension/placeholder.test.ts +1 -1
  54. package/src/parser/block/extension/table.test.ts +1 -1
  55. package/src/parser/block/extension/table.ts +1 -1
  56. package/src/parser/block/extension.test.ts +1 -1
  57. package/src/parser/block/heading.test.ts +3 -3
  58. package/src/parser/block/heading.ts +1 -1
  59. package/src/parser/block/horizontalrule.test.ts +1 -1
  60. package/src/parser/block/ilist.test.ts +1 -1
  61. package/src/parser/block/mathblock.test.ts +1 -1
  62. package/src/parser/block/olist.test.ts +1 -1
  63. package/src/parser/block/olist.ts +5 -5
  64. package/src/parser/block/paragraph.test.ts +1 -1
  65. package/src/parser/block/reply/cite.test.ts +1 -1
  66. package/src/parser/block/reply/cite.ts +2 -2
  67. package/src/parser/block/reply/quote.test.ts +1 -1
  68. package/src/parser/block/reply/quote.ts +2 -2
  69. package/src/parser/block/reply.test.ts +1 -1
  70. package/src/parser/block/sidefence.test.ts +1 -1
  71. package/src/parser/block/table.test.ts +1 -1
  72. package/src/parser/block/table.ts +1 -1
  73. package/src/parser/block/ulist.test.ts +1 -1
  74. package/src/parser/block/ulist.ts +1 -1
  75. package/src/parser/block.ts +2 -2
  76. package/src/parser/header.test.ts +1 -1
  77. package/src/parser/header.ts +3 -3
  78. package/src/parser/inline/annotation.test.ts +1 -1
  79. package/src/parser/inline/autolink/account.test.ts +1 -1
  80. package/src/parser/inline/autolink/anchor.test.ts +1 -1
  81. package/src/parser/inline/autolink/channel.test.ts +1 -1
  82. package/src/parser/inline/autolink/email.test.ts +1 -1
  83. package/src/parser/inline/autolink/email.ts +1 -1
  84. package/src/parser/inline/autolink/hashnum.test.ts +1 -1
  85. package/src/parser/inline/autolink/hashtag.test.ts +1 -1
  86. package/src/parser/inline/autolink/url.test.ts +1 -1
  87. package/src/parser/inline/bracket.test.ts +1 -1
  88. package/src/parser/inline/code.test.ts +1 -1
  89. package/src/parser/inline/code.ts +2 -2
  90. package/src/parser/inline/comment.test.ts +1 -1
  91. package/src/parser/inline/deletion.test.ts +1 -1
  92. package/src/parser/inline/emphasis.test.ts +1 -1
  93. package/src/parser/inline/emstrong.ts +2 -2
  94. package/src/parser/inline/escape.ts +3 -3
  95. package/src/parser/inline/extension/index.test.ts +1 -1
  96. package/src/parser/inline/extension/indexer.test.ts +1 -1
  97. package/src/parser/inline/extension/label.test.ts +1 -1
  98. package/src/parser/inline/extension/placeholder.test.ts +1 -1
  99. package/src/parser/inline/html.test.ts +1 -1
  100. package/src/parser/inline/htmlentity.test.ts +1 -1
  101. package/src/parser/inline/htmlentity.ts +1 -1
  102. package/src/parser/inline/insertion.test.ts +1 -1
  103. package/src/parser/inline/link.test.ts +1 -1
  104. package/src/parser/inline/link.ts +1 -1
  105. package/src/parser/inline/mark.test.ts +1 -1
  106. package/src/parser/inline/math.test.ts +1 -1
  107. package/src/parser/inline/math.ts +1 -1
  108. package/src/parser/inline/media.test.ts +1 -1
  109. package/src/parser/inline/media.ts +2 -2
  110. package/src/parser/inline/reference.test.ts +1 -1
  111. package/src/parser/inline/ruby.test.ts +1 -1
  112. package/src/parser/inline/ruby.ts +3 -3
  113. package/src/parser/inline/shortmedia.test.ts +1 -1
  114. package/src/parser/inline/strong.test.ts +1 -1
  115. package/src/parser/inline/template.test.ts +1 -1
  116. package/src/parser/inline.test.ts +1 -1
  117. package/src/parser/locale.test.ts +1 -1
  118. package/src/parser/segment.ts +1 -1
  119. package/src/parser/source/escapable.test.ts +1 -1
  120. package/src/parser/source/escapable.ts +1 -1
  121. package/src/parser/source/line.test.ts +1 -1
  122. package/src/parser/source/line.ts +2 -2
  123. package/src/parser/source/str.ts +4 -4
  124. package/src/parser/source/text.test.ts +1 -1
  125. package/src/parser/source/text.ts +3 -3
  126. package/src/parser/source/unescapable.test.ts +1 -1
  127. package/src/parser/source/unescapable.ts +1 -1
  128. package/src/parser/visibility.ts +13 -13
  129. 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.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
  });
@@ -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,13 @@ 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, input => {
62
+ const { source, context } = input;
62
63
  if (source === '') return;
63
64
  const memo = context.memo ??= new Memo();
64
65
  context.memorable ??= ~0;
65
- const position = source.length;
66
+ context.offset ??= 0;
67
+ const position = source.length + context.offset!;
66
68
  const st0 = context.state ?? 0;
67
69
  const st1 = context.state = st0 | state;
68
70
  const cache = syntax && memo.get(position, syntax, st1);
@@ -70,7 +72,7 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
70
72
  ? cache.length === 0
71
73
  ? undefined
72
74
  : [cache[0], source.slice(cache[1])]
73
- : parser!(source, context);
75
+ : parser!(input);
74
76
  if (syntax && st0 & context.memorable!) {
75
77
  cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
76
78
  assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
@@ -89,12 +91,12 @@ export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
89
91
  export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
90
92
  if (typeof cost === 'function') return creation(1, cost);
91
93
  assert(cost > 0);
92
- return (source, context) => {
93
- const { resources = { clock: 1, recursion: 1 } } = context;
94
+ return input => {
95
+ const resources = input.context.resources ?? { clock: 1, recursion: 1 };
94
96
  if (resources.clock <= 0) throw new Error('Too many creations');
95
97
  if (resources.recursion <= 0) throw new Error('Too much recursion');
96
98
  --resources.recursion;
97
- const result = parser!(source, context);
99
+ const result = parser!(input);
98
100
  ++resources.recursion;
99
101
  if (result) {
100
102
  resources.clock -= cost;
@@ -106,10 +108,11 @@ export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown
106
108
  export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
107
109
  export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
108
110
  assert(precedence > 0);
109
- return (source, context) => {
111
+ return input => {
112
+ const { context } = input;
110
113
  const p = context.precedence;
111
114
  context.precedence = precedence;
112
- const result = parser(source, context);
115
+ const result = parser(input);
113
116
  context.precedence = p;
114
117
  return result;
115
118
  };
@@ -117,9 +120,9 @@ export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T>
117
120
 
118
121
  export function guard<P extends Parser<unknown>>(f: (context: Context<P>) => boolean | number, parser: P): P;
119
122
  export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T>): Parser<T> {
120
- return (source, context) =>
121
- f(context)
122
- ? parser(source, context)
123
+ return input =>
124
+ f(input.context)
125
+ ? parser(input)
123
126
  : undefined;
124
127
  }
125
128
 
@@ -131,12 +134,13 @@ export function constraint<T>(state: number, positive: boolean | Parser<T>, pars
131
134
  positive = true;
132
135
  }
133
136
  assert(state);
134
- return (source, context) => {
137
+ return input => {
138
+ const { context } = input;
135
139
  const s = positive
136
140
  ? state & context.state!
137
141
  : state & ~context.state!;
138
142
  return s === state
139
- ? parser!(source, context)
143
+ ? parser!(input)
140
144
  : undefined;
141
145
  };
142
146
  }
@@ -149,12 +153,13 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
149
153
  positive = true;
150
154
  }
151
155
  assert(state);
152
- return (source, context) => {
156
+ return input => {
157
+ const { context } = input;
153
158
  const s = context.state ?? 0;
154
159
  context.state = positive
155
160
  ? s | state
156
161
  : s & ~state;
157
- const result = parser!(source, context);
162
+ const result = parser!(input);
158
163
  context.state = s;
159
164
  return result;
160
165
  };
@@ -6,13 +6,14 @@ 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 input => {
10
+ const { source, context } = input;
10
11
  let rest = source;
11
12
  let nodes: T[] | undefined;
12
13
  for (let i = 0, len = parsers.length; i < len; ++i) {
13
14
  if (rest === '') break;
14
15
  if (context.delimiters?.match(rest, context.precedence)) break;
15
- const result = parsers[i](rest, context);
16
+ const result = parsers[i]({ source: rest, context });
16
17
  assert(check(rest, result));
17
18
  if (!result) break;
18
19
  nodes = nodes