securemark 0.259.0 → 0.260.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/design.md +16 -5
  3. package/dist/index.js +625 -318
  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 +33 -33
  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 +3 -3
  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 +3 -3
  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 +23 -13
  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/annotation.ts +3 -4
  82. package/src/parser/inline/autolink/account.test.ts +12 -12
  83. package/src/parser/inline/autolink/account.ts +4 -2
  84. package/src/parser/inline/autolink/anchor.test.ts +10 -10
  85. package/src/parser/inline/autolink/anchor.ts +14 -11
  86. package/src/parser/inline/autolink/channel.test.ts +4 -4
  87. package/src/parser/inline/autolink/email.test.ts +1 -1
  88. package/src/parser/inline/autolink/email.ts +1 -1
  89. package/src/parser/inline/autolink/hashnum.test.ts +1 -1
  90. package/src/parser/inline/autolink/hashnum.ts +4 -2
  91. package/src/parser/inline/autolink/hashtag.test.ts +21 -21
  92. package/src/parser/inline/autolink/hashtag.ts +4 -2
  93. package/src/parser/inline/autolink/url.test.ts +56 -56
  94. package/src/parser/inline/autolink/url.ts +5 -3
  95. package/src/parser/inline/autolink.ts +1 -1
  96. package/src/parser/inline/bracket.test.ts +1 -1
  97. package/src/parser/inline/bracket.ts +8 -8
  98. package/src/parser/inline/code.test.ts +1 -1
  99. package/src/parser/inline/code.ts +2 -2
  100. package/src/parser/inline/comment.test.ts +1 -1
  101. package/src/parser/inline/comment.ts +2 -2
  102. package/src/parser/inline/deletion.test.ts +1 -1
  103. package/src/parser/inline/deletion.ts +2 -2
  104. package/src/parser/inline/emphasis.test.ts +1 -1
  105. package/src/parser/inline/emphasis.ts +2 -2
  106. package/src/parser/inline/emstrong.ts +4 -4
  107. package/src/parser/inline/escape.ts +3 -3
  108. package/src/parser/inline/extension/index.test.ts +1 -1
  109. package/src/parser/inline/extension/index.ts +3 -4
  110. package/src/parser/inline/extension/indexer.test.ts +1 -1
  111. package/src/parser/inline/extension/label.test.ts +1 -1
  112. package/src/parser/inline/extension/placeholder.test.ts +1 -1
  113. package/src/parser/inline/extension/placeholder.ts +2 -2
  114. package/src/parser/inline/html.test.ts +1 -3
  115. package/src/parser/inline/html.ts +3 -3
  116. package/src/parser/inline/htmlentity.test.ts +1 -1
  117. package/src/parser/inline/htmlentity.ts +1 -1
  118. package/src/parser/inline/insertion.test.ts +1 -1
  119. package/src/parser/inline/insertion.ts +2 -2
  120. package/src/parser/inline/link.test.ts +111 -111
  121. package/src/parser/inline/link.ts +12 -12
  122. package/src/parser/inline/mark.test.ts +1 -1
  123. package/src/parser/inline/mark.ts +2 -2
  124. package/src/parser/inline/math.test.ts +1 -1
  125. package/src/parser/inline/math.ts +1 -1
  126. package/src/parser/inline/media.test.ts +2 -1
  127. package/src/parser/inline/media.ts +4 -4
  128. package/src/parser/inline/reference.test.ts +4 -4
  129. package/src/parser/inline/reference.ts +3 -4
  130. package/src/parser/inline/ruby.test.ts +1 -1
  131. package/src/parser/inline/ruby.ts +5 -5
  132. package/src/parser/inline/shortmedia.test.ts +1 -1
  133. package/src/parser/inline/strong.test.ts +1 -1
  134. package/src/parser/inline/strong.ts +2 -2
  135. package/src/parser/inline/template.test.ts +1 -1
  136. package/src/parser/inline/template.ts +2 -2
  137. package/src/parser/inline.test.ts +52 -52
  138. package/src/parser/locale.test.ts +1 -1
  139. package/src/parser/segment.ts +1 -1
  140. package/src/parser/source/escapable.test.ts +1 -1
  141. package/src/parser/source/escapable.ts +1 -1
  142. package/src/parser/source/line.test.ts +1 -1
  143. package/src/parser/source/line.ts +2 -2
  144. package/src/parser/source/str.ts +4 -4
  145. package/src/parser/source/text.test.ts +1 -1
  146. package/src/parser/source/text.ts +4 -4
  147. package/src/parser/source/unescapable.test.ts +1 -1
  148. package/src/parser/source/unescapable.ts +1 -1
  149. package/src/parser/visibility.ts +13 -13
  150. package/src/renderer/render/media/pdf.ts +1 -0
  151. package/src/util/info.ts +2 -4
  152. 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.0",
3
+ "version": "0.260.0",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -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];
@@ -56,47 +56,46 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
56
56
  return result;
57
57
  }
58
58
 
59
- export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, parser: P): P;
60
- export function syntax<T>(syntax: number, precedence: number, cost: number, parser?: Parser<T>): Parser<T> {
61
- return creation(cost, (source, context) => {
59
+ export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, state: number, parser: P): P;
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 }) => {
62
62
  if (source === '') return;
63
63
  const memo = context.memo ??= new Memo();
64
64
  context.memorable ??= ~0;
65
- const p = context.precedence;
66
- context.precedence = precedence;
67
- const position = source.length;
68
- const state = context.state ?? 0;
69
- const cache = syntax && memo.get(position, syntax, state);
65
+ context.offset ??= 0;
66
+ const position = source.length + context.offset!;
67
+ const st0 = context.state ?? 0;
68
+ const st1 = context.state = st0 | state;
69
+ const cache = syntax && memo.get(position, syntax, st1);
70
70
  const result: Result<T> = cache
71
71
  ? cache.length === 0
72
72
  ? undefined
73
73
  : [cache[0], source.slice(cache[1])]
74
- : parser!(source, context);
75
- if (syntax && state & context.memorable!) {
76
- cache ?? memo.set(position, syntax, state, eval(result), source.length - exec(result, '').length);
77
- assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, state));
74
+ : parser!({ source, context });
75
+ if (syntax && st0 & context.memorable!) {
76
+ cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
77
+ assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
78
78
  }
79
- if (result && !state && memo.length! >= position) {
80
- assert(!(state & context.memorable!));
81
- memo.clear(position);
79
+ if (result && !st0 && memo.length! >= position + 2) {
80
+ assert(!(st0 & context.memorable!));
81
+ memo.clear(position + 2);
82
82
  }
83
- context.precedence = p;
83
+ context.state = st0;
84
84
  return result;
85
- });
85
+ }));
86
86
  }
87
87
 
88
88
  export function creation<P extends Parser<unknown>>(parser: P): P;
89
89
  export function creation<P extends Parser<unknown>>(cost: number, parser: P): P;
90
90
  export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown>): Parser<unknown> {
91
91
  if (typeof cost === 'function') return creation(1, cost);
92
- if (cost === 0) return parser!;
93
- assert(cost >= 0);
94
- return (source, context) => {
95
- const { resources = { clock: 1, recursion: 1 } } = context;
92
+ assert(cost > 0);
93
+ return ({ source, context }) => {
94
+ const resources = context.resources ?? { clock: 1, recursion: 1 };
96
95
  if (resources.clock <= 0) throw new Error('Too many creations');
97
96
  if (resources.recursion <= 0) throw new Error('Too much recursion');
98
97
  --resources.recursion;
99
- const result = parser!(source, context);
98
+ const result = parser!({ source, context });
100
99
  ++resources.recursion;
101
100
  if (result) {
102
101
  resources.clock -= cost;
@@ -107,10 +106,11 @@ export function creation(cost: number | Parser<unknown>, parser?: Parser<unknown
107
106
 
108
107
  export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
109
108
  export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
110
- return (source, context) => {
109
+ assert(precedence > 0);
110
+ return ({ source, context }) => {
111
111
  const p = context.precedence;
112
112
  context.precedence = precedence;
113
- const result = parser(source, context);
113
+ const result = parser({ source, context });
114
114
  context.precedence = p;
115
115
  return result;
116
116
  };
@@ -118,9 +118,9 @@ export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T>
118
118
 
119
119
  export function guard<P extends Parser<unknown>>(f: (context: Context<P>) => boolean | number, parser: P): P;
120
120
  export function guard<T>(f: (context: Ctx) => boolean | number, parser: Parser<T>): Parser<T> {
121
- return (source, context) =>
121
+ return ({ source, context }) =>
122
122
  f(context)
123
- ? parser(source, context)
123
+ ? parser({ source, context })
124
124
  : undefined;
125
125
  }
126
126
 
@@ -132,12 +132,12 @@ export function constraint<T>(state: number, positive: boolean | Parser<T>, pars
132
132
  positive = true;
133
133
  }
134
134
  assert(state);
135
- return (source, context) => {
135
+ return ({ source, context }) => {
136
136
  const s = positive
137
137
  ? state & context.state!
138
138
  : state & ~context.state!;
139
139
  return s === state
140
- ? parser!(source, context)
140
+ ? parser!({ source, context })
141
141
  : undefined;
142
142
  };
143
143
  }
@@ -150,12 +150,12 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
150
150
  positive = true;
151
151
  }
152
152
  assert(state);
153
- return (source, context) => {
153
+ return ({ source, context }) => {
154
154
  const s = context.state ?? 0;
155
155
  context.state = positive
156
156
  ? s | state
157
157
  : s & ~state;
158
- const result = parser!(source, context);
158
+ const result = parser!({ source, context });
159
159
  context.state = s;
160
160
  return result;
161
161
  };