securemark 0.291.1 → 0.292.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 (164) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/design.md +13 -2
  3. package/dist/index.js +1093 -756
  4. package/markdown.d.ts +17 -17
  5. package/package.json +1 -1
  6. package/src/combinator/control/constraint/block.test.ts +8 -5
  7. package/src/combinator/control/constraint/block.ts +9 -9
  8. package/src/combinator/control/constraint/contract.ts +20 -28
  9. package/src/combinator/control/constraint/line.test.ts +9 -6
  10. package/src/combinator/control/constraint/line.ts +21 -22
  11. package/src/combinator/control/manipulation/convert.ts +29 -13
  12. package/src/combinator/control/manipulation/fence.ts +18 -15
  13. package/src/combinator/control/manipulation/indent.test.ts +17 -14
  14. package/src/combinator/control/manipulation/indent.ts +15 -8
  15. package/src/combinator/control/manipulation/match.ts +11 -10
  16. package/src/combinator/control/manipulation/recovery.ts +6 -2
  17. package/src/combinator/control/manipulation/scope.ts +37 -38
  18. package/src/combinator/control/manipulation/surround.ts +78 -60
  19. package/src/combinator/control/manipulation/trim.test.ts +12 -9
  20. package/src/combinator/control/monad/bind.ts +16 -16
  21. package/src/combinator/control/monad/fmap.ts +6 -6
  22. package/src/combinator/data/parser/context/delimiter.ts +8 -7
  23. package/src/combinator/data/parser/context.test.ts +19 -14
  24. package/src/combinator/data/parser/context.ts +20 -16
  25. package/src/combinator/data/parser/inits.ts +13 -14
  26. package/src/combinator/data/parser/sequence.test.ts +16 -15
  27. package/src/combinator/data/parser/sequence.ts +13 -14
  28. package/src/combinator/data/parser/some.test.ts +19 -18
  29. package/src/combinator/data/parser/some.ts +13 -15
  30. package/src/combinator/data/parser/subsequence.test.ts +22 -21
  31. package/src/combinator/data/parser/subsequence.ts +3 -3
  32. package/src/combinator/data/parser/tails.ts +3 -3
  33. package/src/combinator/data/parser/union.test.ts +16 -15
  34. package/src/combinator/data/parser/union.ts +2 -2
  35. package/src/combinator/data/parser.ts +66 -28
  36. package/src/debug.test.ts +3 -3
  37. package/src/parser/api/bind.ts +3 -3
  38. package/src/parser/api/header.ts +7 -6
  39. package/src/parser/api/normalize.ts +2 -2
  40. package/src/parser/api/parse.test.ts +14 -15
  41. package/src/parser/api/parse.ts +3 -3
  42. package/src/parser/autolink.test.ts +19 -17
  43. package/src/parser/block/blockquote.test.ts +86 -84
  44. package/src/parser/block/blockquote.ts +4 -2
  45. package/src/parser/block/codeblock.test.ts +58 -56
  46. package/src/parser/block/codeblock.ts +3 -3
  47. package/src/parser/block/dlist.test.ts +58 -56
  48. package/src/parser/block/extension/aside.test.ts +10 -8
  49. package/src/parser/block/extension/aside.ts +1 -1
  50. package/src/parser/block/extension/example.test.ts +20 -18
  51. package/src/parser/block/extension/example.ts +3 -3
  52. package/src/parser/block/extension/fig.test.ts +38 -36
  53. package/src/parser/block/extension/fig.ts +1 -1
  54. package/src/parser/block/extension/figbase.test.ts +17 -15
  55. package/src/parser/block/extension/figure.test.ts +64 -62
  56. package/src/parser/block/extension/figure.ts +3 -2
  57. package/src/parser/block/extension/message.test.ts +15 -13
  58. package/src/parser/block/extension/message.ts +3 -3
  59. package/src/parser/block/extension/placeholder.test.ts +3 -1
  60. package/src/parser/block/extension/table.test.ts +73 -71
  61. package/src/parser/block/extension/table.ts +5 -5
  62. package/src/parser/block/extension.test.ts +3 -1
  63. package/src/parser/block/heading.test.ts +65 -64
  64. package/src/parser/block/heading.ts +3 -3
  65. package/src/parser/block/ilist.test.ts +3 -1
  66. package/src/parser/block/ilist.ts +3 -3
  67. package/src/parser/block/mathblock.test.ts +33 -31
  68. package/src/parser/block/mathblock.ts +1 -1
  69. package/src/parser/block/mediablock.ts +2 -2
  70. package/src/parser/block/olist.test.ts +99 -97
  71. package/src/parser/block/olist.ts +2 -2
  72. package/src/parser/block/pagebreak.test.ts +17 -15
  73. package/src/parser/block/pagebreak.ts +1 -1
  74. package/src/parser/block/paragraph.test.ts +60 -57
  75. package/src/parser/block/reply/cite.test.ts +41 -39
  76. package/src/parser/block/reply/cite.ts +3 -3
  77. package/src/parser/block/reply/quote.test.ts +52 -50
  78. package/src/parser/block/reply.test.ts +21 -19
  79. package/src/parser/block/sidefence.test.ts +51 -49
  80. package/src/parser/block/table.test.ts +51 -50
  81. package/src/parser/block/table.ts +6 -6
  82. package/src/parser/block/ulist.test.ts +52 -50
  83. package/src/parser/block/ulist.ts +2 -2
  84. package/src/parser/block.ts +6 -5
  85. package/src/parser/context.ts +1 -0
  86. package/src/parser/header.test.ts +22 -21
  87. package/src/parser/header.ts +25 -13
  88. package/src/parser/inline/annotation.test.ts +44 -42
  89. package/src/parser/inline/annotation.ts +2 -2
  90. package/src/parser/inline/autolink/account.test.ts +32 -30
  91. package/src/parser/inline/autolink/account.ts +1 -1
  92. package/src/parser/inline/autolink/anchor.test.ts +23 -21
  93. package/src/parser/inline/autolink/anchor.ts +1 -1
  94. package/src/parser/inline/autolink/channel.test.ts +16 -14
  95. package/src/parser/inline/autolink/channel.ts +2 -2
  96. package/src/parser/inline/autolink/email.test.ts +38 -36
  97. package/src/parser/inline/autolink/email.ts +2 -2
  98. package/src/parser/inline/autolink/hashnum.test.ts +39 -37
  99. package/src/parser/inline/autolink/hashnum.ts +1 -1
  100. package/src/parser/inline/autolink/hashtag.test.ts +58 -56
  101. package/src/parser/inline/autolink/hashtag.ts +1 -1
  102. package/src/parser/inline/autolink/url.test.ts +76 -74
  103. package/src/parser/inline/autolink/url.ts +6 -6
  104. package/src/parser/inline/bracket.test.ts +69 -67
  105. package/src/parser/inline/bracket.ts +32 -32
  106. package/src/parser/inline/code.test.ts +32 -29
  107. package/src/parser/inline/code.ts +20 -13
  108. package/src/parser/inline/deletion.test.ts +29 -27
  109. package/src/parser/inline/deletion.ts +2 -2
  110. package/src/parser/inline/emphasis.test.ts +40 -36
  111. package/src/parser/inline/emphasis.ts +2 -2
  112. package/src/parser/inline/emstrong.test.ts +102 -96
  113. package/src/parser/inline/emstrong.ts +96 -36
  114. package/src/parser/inline/extension/index.test.ts +91 -89
  115. package/src/parser/inline/extension/index.ts +18 -30
  116. package/src/parser/inline/extension/indexee.ts +1 -1
  117. package/src/parser/inline/extension/indexer.test.ts +26 -24
  118. package/src/parser/inline/extension/indexer.ts +1 -1
  119. package/src/parser/inline/extension/label.test.ts +34 -32
  120. package/src/parser/inline/extension/placeholder.test.ts +44 -42
  121. package/src/parser/inline/extension/placeholder.ts +11 -8
  122. package/src/parser/inline/html.test.ts +108 -106
  123. package/src/parser/inline/html.ts +24 -23
  124. package/src/parser/inline/htmlentity.test.ts +39 -37
  125. package/src/parser/inline/htmlentity.ts +9 -3
  126. package/src/parser/inline/insertion.test.ts +29 -27
  127. package/src/parser/inline/insertion.ts +2 -2
  128. package/src/parser/inline/italic.test.ts +55 -53
  129. package/src/parser/inline/italic.ts +2 -2
  130. package/src/parser/inline/link.test.ts +187 -185
  131. package/src/parser/inline/link.ts +30 -12
  132. package/src/parser/inline/mark.test.ts +31 -29
  133. package/src/parser/inline/mark.ts +3 -3
  134. package/src/parser/inline/math.test.ts +133 -131
  135. package/src/parser/inline/math.ts +2 -2
  136. package/src/parser/inline/media.test.ts +93 -91
  137. package/src/parser/inline/media.ts +41 -17
  138. package/src/parser/inline/reference.test.ts +110 -108
  139. package/src/parser/inline/reference.ts +48 -39
  140. package/src/parser/inline/remark.test.ts +53 -51
  141. package/src/parser/inline/remark.ts +3 -3
  142. package/src/parser/inline/ruby.test.ts +46 -44
  143. package/src/parser/inline/ruby.ts +24 -27
  144. package/src/parser/inline/shortmedia.test.ts +11 -9
  145. package/src/parser/inline/strong.test.ts +37 -33
  146. package/src/parser/inline/strong.ts +6 -3
  147. package/src/parser/inline/template.test.ts +24 -22
  148. package/src/parser/inline/template.ts +20 -11
  149. package/src/parser/inline.test.ts +221 -220
  150. package/src/parser/inline.ts +13 -8
  151. package/src/parser/segment.ts +13 -8
  152. package/src/parser/source/escapable.test.ts +24 -22
  153. package/src/parser/source/escapable.ts +26 -41
  154. package/src/parser/source/line.test.ts +19 -17
  155. package/src/parser/source/line.ts +3 -3
  156. package/src/parser/source/str.ts +28 -11
  157. package/src/parser/source/text.test.ts +85 -83
  158. package/src/parser/source/text.ts +26 -54
  159. package/src/parser/source/unescapable.test.ts +24 -22
  160. package/src/parser/source/unescapable.ts +18 -33
  161. package/src/parser/source.ts +1 -1
  162. package/src/parser/util.ts +36 -33
  163. package/src/parser/visibility.ts +19 -15
  164. package/src/util/quote.ts +4 -2
package/markdown.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { Parser, Ctx } from './src/combinator/data/parser';
2
- import { Command } from './src/parser/context';
1
+ import { Parser, Ctx, CtxOptions } from './src/combinator/data/parser';
3
2
  import { Dict } from 'spica/dict';
4
3
 
5
4
  declare abstract class Markdown<T> {
@@ -13,7 +12,10 @@ export interface MarkdownParser extends
13
12
  ]> {
14
13
  }
15
14
  export namespace MarkdownParser {
16
- export interface Context extends Ctx {
15
+ export interface Context extends Ctx, Options {
16
+ buffer?: (string | HTMLElement)[];
17
+ }
18
+ export interface Options extends CtxOptions {
17
19
  readonly host?: URL;
18
20
  readonly url?: URL;
19
21
  readonly id?: string;
@@ -732,18 +734,10 @@ export namespace MarkdownParser {
732
734
  export interface SignatureParser extends
733
735
  Inline<'extension/index/signature'>,
734
736
  Parser<string | HTMLElement, Context, [
735
- InlineParser,
737
+ UnsafeHTMLEntityParser,
738
+ SourceParser.TxtParser,
736
739
  ]> {
737
740
  }
738
- export namespace SignatureParser {
739
- export interface InternalParser extends
740
- Inline<'extension/index/signature/internal'>,
741
- Parser<string, Context, [
742
- UnsafeHTMLEntityParser,
743
- SourceParser.TxtParser,
744
- ]> {
745
- }
746
- }
747
741
  }
748
742
  export interface IndexerParser extends
749
743
  // [|signature]
@@ -784,7 +778,7 @@ export namespace MarkdownParser {
784
778
  // { uri }
785
779
  // [abc]{uri nofollow}
786
780
  Inline<'link'>,
787
- Parser<HTMLAnchorElement, Context, [
781
+ Parser<HTMLAnchorElement | HTMLSpanElement, Context, [
788
782
  LinkParser.MediaLinkParser,
789
783
  LinkParser.TextLinkParser,
790
784
  ]> {
@@ -798,7 +792,7 @@ export namespace MarkdownParser {
798
792
  }
799
793
  export interface TextLinkParser extends
800
794
  Inline<'link/textlink'>,
801
- Parser<HTMLAnchorElement, Context, [
795
+ Parser<HTMLAnchorElement | HTMLSpanElement, Context, [
802
796
  Parser<(HTMLElement | string)[], Context, [
803
797
  InlineParser,
804
798
  ]>,
@@ -807,7 +801,7 @@ export namespace MarkdownParser {
807
801
  }
808
802
  export interface MediaLinkParser extends
809
803
  Inline<'link/medialink'>,
810
- Parser<HTMLAnchorElement, Context, [
804
+ Parser<HTMLAnchorElement | HTMLSpanElement, Context, [
811
805
  Parser<HTMLElement[], Context, [
812
806
  MediaParser,
813
807
  ShortMediaParser,
@@ -1003,17 +997,23 @@ export namespace MarkdownParser {
1003
997
  Inline<'emstrong'>,
1004
998
  Parser<HTMLElement | string, Context, [
1005
999
  InlineParser,
1006
- InlineParser,
1000
+ Parser<HTMLElement | string, Context, [
1001
+ EmStrongParser,
1002
+ StrongParser,
1003
+ EmphasisParser,
1004
+ ]>,
1007
1005
  ]> {
1008
1006
  }
1009
1007
  export interface StrongParser extends
1010
1008
  // **abc**
1011
1009
  Inline<'strong'>,
1012
1010
  Parser<HTMLElement | string, Context, [
1011
+ EmphasisParser,
1013
1012
  InlineParser,
1014
1013
  Parser<HTMLElement | string, Context, [
1015
1014
  EmStrongParser,
1016
1015
  StrongParser,
1016
+ EmphasisParser,
1017
1017
  ]>,
1018
1018
  ]> {
1019
1019
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.291.1",
3
+ "version": "0.292.0",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -1,17 +1,20 @@
1
1
  import { block } from './block';
2
+ import { input } from '../../data/parser';
2
3
  import { inspect } from '../../../debug.test';
3
4
 
4
5
  describe('Unit: combinator/block', () => {
5
6
  describe('block', () => {
7
+ const { context: ctx } = input('', {});
8
+
6
9
  it('invalid', () => {
7
- assert.throws(() => block(_ => [[], '\n'])({ source: ' \n', context: {} }));
10
+ assert.throws(() => block(_ => [[]])(input(' \n', ctx)));
8
11
  });
9
12
 
10
13
  it('valid', () => {
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']);
14
+ assert.deepStrictEqual(inspect(block(({ context }) => { context.position = context.source.length; return [[]]; })(input('\n', ctx)), ctx), [[], '']);
15
+ assert.deepStrictEqual(inspect(block(({ context }) => { context.position = context.source.length; return [[]]; })(input(' \n', ctx)), ctx), [[], '']);
16
+ assert.deepStrictEqual(inspect(block(({ context }) => { context.position = context.source.length; return [[]]; })(input('\n\n', ctx)), ctx), [[], '']);
17
+ assert.deepStrictEqual(inspect(block(({ context }) => { context.position = context.source.length - 1; return [[]]; })(input('\n\n', ctx)), ctx), [[], '\n']);
15
18
  });
16
19
 
17
20
  });
@@ -1,19 +1,19 @@
1
- import { Parser, exec } from '../../data/parser';
1
+ import { Parser, failsafe } from '../../data/parser';
2
2
  import { firstline, isBlank } from './line';
3
3
 
4
4
  export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
5
5
  export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
6
6
  assert(parser);
7
- return input => {
8
- const { source } = input;
9
- if (source === '') return;
7
+ return failsafe(input => {
8
+ const { context } = input;
9
+ const { source, position } = context;
10
+ if (position === source.length) return;
10
11
  const result = parser(input);
11
12
  if (result === undefined) return;
12
- const rest = exec(result);
13
- if (separation && !isBlank(firstline(rest))) return;
14
- assert(rest === '' || source[source.length - rest.length - 1] === '\n');
15
- return rest === '' || source[source.length - rest.length - 1] === '\n'
13
+ if (separation && !isBlank(firstline(source, context.position))) return;
14
+ assert(context.position === source.length || source[context.position - 1] === '\n');
15
+ return context.position === source.length || source[context.position - 1] === '\n'
16
16
  ? result
17
17
  : undefined;
18
- };
18
+ });
19
19
  }
@@ -1,5 +1,5 @@
1
1
  import { isArray } from 'spica/alias';
2
- import { Parser, Input, Ctx, Node, Context, eval, exec, check } from '../../data/parser';
2
+ import { Parser, Input, Ctx, Node, Context, eval, failsafe } from '../../data/parser';
3
3
 
4
4
  //export function contract<P extends Parser<unknown>>(patterns: string | RegExp | (string | RegExp)[], parser: P, cond: (nodes: readonly Data<P>[], rest: string) => boolean): P;
5
5
  //export function contract<N>(patterns: string | RegExp | (string | RegExp)[], parser: Parser<N>, cond: (nodes: readonly N[], rest: string) => boolean): Parser<N> {
@@ -13,24 +13,19 @@ export function validate<N>(patterns: string | RegExp | (string | RegExp)[] | ((
13
13
  if (!isArray(patterns)) return validate([patterns], parser);
14
14
  assert(patterns.length > 0);
15
15
  assert(patterns.every(pattern => pattern instanceof RegExp ? !pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^') : true));
16
- const match: (source: string) => boolean = global.eval([
17
- 'source =>',
16
+ const match: (context: Ctx) => boolean = global.eval([
17
+ '({ source, position }) =>',
18
18
  patterns.map(pattern =>
19
19
  typeof pattern === 'string'
20
- ? `|| source.slice(0, ${pattern.length}) === '${pattern}'`
21
- : `|| /${pattern.source}/${pattern.flags}.test(source)`).join('').slice(2),
20
+ ? `|| source.startsWith('${pattern}', position)`
21
+ : `|| /${pattern.source}/${pattern.flags}.test(source.slice(position))`).join('').slice(2),
22
22
  ].join(''));
23
23
  return input => {
24
- const { source } = input;
25
- if (source === '') return;
26
- if (!match(source)) return;
27
- const result = parser(input);
28
- assert(check(source, result));
29
- if (result === undefined) return;
30
- assert(exec(result).length < source.length);
31
- return exec(result).length < source.length
32
- ? result
33
- : undefined;
24
+ const { context } = input;
25
+ const { source, position } = context;
26
+ if (position === source.length) return;
27
+ if (!match(context)) return;
28
+ return parser(input);
34
29
  };
35
30
  }
36
31
 
@@ -42,19 +37,16 @@ function guard<N>(f: (input: Input<Ctx>) => boolean, parser: Parser<N>): Parser<
42
37
  : undefined;
43
38
  }
44
39
 
45
- export function verify<P extends Parser<unknown>>(parser: P, cond: (nodes: readonly Node<P>[], rest: string, context: Context<P>) => boolean): P;
46
- export function verify<N>(parser: Parser<N>, cond: (nodes: readonly N[], rest: string, context: Ctx) => boolean): Parser<N> {
40
+ export function verify<P extends Parser<unknown>>(parser: P, cond: (nodes: readonly Node<P>[], context: Context<P>) => boolean): P;
41
+ export function verify<N>(parser: Parser<N>, cond: (nodes: readonly N[], context: Ctx) => boolean): Parser<N> {
47
42
  assert(parser);
48
- return input => {
49
- const { source, context } = input;
50
- if (source === '') return;
43
+ return failsafe(input => {
44
+ const { context } = input;
45
+ const { source, position } = context;
46
+ if (position === source.length) return;
51
47
  const result = parser(input);
52
- assert(check(source, result));
53
- if (result === undefined) return;
54
- if (!cond(eval(result), exec(result), context)) return;
55
- assert(exec(result).length < source.length);
56
- return exec(result).length < source.length
57
- ? result
58
- : undefined;
59
- };
48
+ assert(context.position > position || !result);
49
+ if (result && !cond(eval(result), context)) return;
50
+ return result;
51
+ });
60
52
  }
@@ -1,18 +1,21 @@
1
+ import { input } from '../../data/parser';
1
2
  import { line } from './line';
2
3
  import { inspect } from '../../../debug.test';
3
4
 
4
5
  describe('Unit: combinator/line', () => {
5
6
  describe('line', () => {
7
+ const { context: ctx } = input('', {});
8
+
6
9
  it('invalid', () => {
7
- assert.deepStrictEqual(inspect(line(_ => [[], ''])({ source: '', context: {} })), undefined);
10
+ assert.deepStrictEqual(inspect(line(_ => [[]])(input('', ctx)), ctx), undefined);
8
11
  });
9
12
 
10
13
  it('valid', () => {
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: {} })), [[], '']);
14
+ assert.deepStrictEqual(inspect(line(({ context }) => { context.position = context.source.length; return [[]]; })(input(' ', ctx)), ctx), [[], '']);
15
+ assert.deepStrictEqual(inspect(line(({ context }) => { context.position = context.source.length; return [[]]; })(input('\n', ctx)), ctx), [[], '']);
16
+ assert.deepStrictEqual(inspect(line(({ context }) => { context.position = context.source.length; return [[]]; })(input('\n\n', ctx)), ctx), [[], '\n']);
17
+ assert.deepStrictEqual(inspect(line(({ context }) => { context.position = context.source.length; return [[]]; })(input(' \n', ctx)), ctx), [[], '']);
18
+ assert.deepStrictEqual(inspect(line(({ context }) => { context.position = context.source.length - 1; return [[]]; })(input(' \n', ctx)), ctx), [[], '']);
16
19
  });
17
20
 
18
21
  });
@@ -1,33 +1,32 @@
1
- import { Parser, eval, exec, check } from '../../data/parser';
1
+ import { Parser, input, eval, failsafe } from '../../data/parser';
2
2
 
3
3
  export function line<P extends Parser<unknown>>(parser: P): P;
4
4
  export function line<N>(parser: Parser<N>): Parser<N> {
5
5
  assert(parser);
6
- return ({ source, context }) => {
7
- if (source === '') return;
8
- const line = firstline(source);
6
+ return failsafe(({ context }) => {
7
+ const { source, position } = context;
8
+ if (position === source.length) return;
9
+ const line = firstline(source, position);
9
10
  context.offset ??= 0;
10
- context.offset += source.length - line.length;
11
- const result = parser({ source: line, context });
12
- assert(check(line, result));
13
- context.offset -= source.length - line.length;
11
+ context.offset += position;
12
+ const result = parser(input(line, context));
13
+ context.position += position;
14
+ context.position += result && context.position === position ? line.length : 0;
15
+ assert(context.position > position || !result);
16
+ context.source = source;
17
+ context.offset -= position;
14
18
  if (result === undefined) return;
15
- return isBlank(exec(result))
16
- ? [eval(result), source.slice(line.length)]
17
- : undefined;
18
- };
19
+ if (!isBlank(source.slice(context.position, position + line.length))) return;
20
+ context.position = position + line.length;
21
+ return [eval(result)];
22
+ });
19
23
  }
20
24
 
21
- export function firstline(source: string): string {
22
- const i = source.indexOf('\n');
23
- switch (i) {
24
- case -1:
25
- return source;
26
- case 0:
27
- return '\n';
28
- default:
29
- return source.slice(0, i + 1);
30
- }
25
+ export function firstline(source: string, position: number): string {
26
+ const i = source.indexOf('\n', position);
27
+ return i === -1
28
+ ? source.slice(position)
29
+ : source.slice(position, i + 1);
31
30
  }
32
31
 
33
32
  export function isBlank(line: string): boolean {
@@ -1,18 +1,34 @@
1
- import { Parser, Ctx, Context, check } from '../../data/parser';
1
+ import { Parser, Ctx, Context, subinput, failsafe } from '../../data/parser';
2
2
 
3
3
  export function convert<P extends Parser<unknown>>(conv: (source: string, context: Context<P>) => string, parser: P, continuous: boolean, empty?: boolean): P;
4
4
  export function convert<N>(conv: (source: string, context: Ctx) => string, parser: Parser<N>, continuous: boolean, empty = false): Parser<N> {
5
5
  assert(parser);
6
- return ({ source, context }) => {
7
- if (source === '') return;
8
- const src = conv(source, context);
9
- if (src === '') return empty ? [[], ''] : undefined;
10
- const { backtracks } = context;
11
- assert(source.endsWith(src) || src.endsWith(source) || !continuous);
12
- context.backtracks = continuous ? backtracks : {};
13
- const result = parser({ source: src, context });
14
- assert(check(src, result));
15
- context.backtracks = backtracks;
16
- return result;
17
- };
6
+ return failsafe(input => {
7
+ const { context } = input;
8
+ const { source, position } = context;
9
+ if (position === source.length) return;
10
+ const src = conv(source.slice(position), context);
11
+ if (src === '') {
12
+ if (!empty) return;
13
+ context.position = source.length;
14
+ return [[]];
15
+ }
16
+ assert(source.endsWith(src) || src.endsWith(source, position) || !continuous);
17
+ if (continuous) {
18
+ context.position += source.length - position - src.length;
19
+ const result = parser(input);
20
+ assert(context.position > position || !result);
21
+ context.source = source;
22
+ return result;
23
+ }
24
+ else {
25
+ const { offset, backtracks } = context;
26
+ const result = parser(subinput(src, context));
27
+ context.position = context.source.length
28
+ assert(context.offset === offset);
29
+ assert(context.source === source);
30
+ assert(context.backtracks === backtracks);
31
+ return result;
32
+ }
33
+ });
18
34
  }
@@ -1,37 +1,40 @@
1
- import { Parser, Ctx } from '../../data/parser';
1
+ import { Parser, Ctx, failsafe } from '../../data/parser';
2
2
  import { firstline, isBlank } from '../constraint/line';
3
3
  import { push } 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 }) => {
7
- if (source === '') return;
8
- const matches = source.match(opener);
6
+ return failsafe(input => {
7
+ const { context } = input;
8
+ const { source, position } = context;
9
+ if (position === source.length) return;
10
+ const matches = source.slice(position).match(opener);
9
11
  if (!matches) return;
10
- assert(matches[0] === firstline(source));
12
+ assert(matches[0] === firstline(source, position));
11
13
  const delim = matches[1];
12
14
  assert(delim && delim === delim.trim());
13
15
  if (matches[0].includes(delim, delim.length)) return;
14
- let rest = source.slice(matches[0].length);
16
+ context.position += matches[0].length;
15
17
  // Prevent annoying parsing in editing.
16
- if (isBlank(firstline(rest)) && firstline(rest.slice(firstline(rest).length)).trimEnd() !== delim) return;
18
+ const secondline = firstline(source, context.position);
19
+ if (isBlank(secondline) && firstline(source, context.position + secondline.length).trimEnd() !== delim) return;
17
20
  let block = '';
18
21
  let closer = '';
19
22
  let overflow = '';
20
23
  for (let count = 1; ; ++count) {
21
- if (rest === '') break;
22
- const line = firstline(rest);
24
+ if (context.position === source.length) break;
25
+ const line = firstline(source, context.position);
23
26
  if ((closer || count > limit + 1) && isBlank(line)) break;
24
27
  if(closer) {
25
28
  overflow += line;
26
29
  }
27
30
  if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
28
31
  closer = line;
29
- if (isBlank(firstline(rest.slice(line.length)))) {
30
- rest = rest.slice(line.length);
32
+ if (isBlank(firstline(source, context.position + line.length))) {
33
+ context.position += line.length;
31
34
  break;
32
35
  }
33
36
  if (!separation) {
34
- rest = rest.slice(line.length);
37
+ context.position += line.length;
35
38
  break;
36
39
  }
37
40
  assert(!overflow);
@@ -40,8 +43,8 @@ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: Reg
40
43
  if (!overflow) {
41
44
  block += line;
42
45
  }
43
- rest = rest.slice(line.length);
46
+ context.position += line.length;
44
47
  }
45
- return [push([block, overflow, closer], matches), rest];
46
- };
48
+ return [push([block, overflow, closer], matches)];
49
+ });
47
50
  }
@@ -1,23 +1,26 @@
1
1
  import { indent } from './indent';
2
+ import { input } from '../../data/parser';
2
3
  import { inspect } from '../../../debug.test';
3
4
 
4
5
  describe('Unit: combinator/indent', () => {
5
6
  describe('indent', () => {
7
+ const { context: ctx } = input('', {});
8
+
6
9
  it('valid', () => {
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'], '']);
10
+ const parser = indent(({ context }) => { context.position = context.source.length; return [[context.source]]; });
11
+ assert.deepStrictEqual(inspect(parser(input('', ctx)), ctx), undefined);
12
+ assert.deepStrictEqual(inspect(parser(input(' ', ctx)), ctx), undefined);
13
+ assert.deepStrictEqual(inspect(parser(input(' ', ctx)), ctx), undefined);
14
+ assert.deepStrictEqual(inspect(parser(input('a ', ctx)), ctx), undefined);
15
+ assert.deepStrictEqual(inspect(parser(input(' a\n', ctx)), ctx), [['a'], '']);
16
+ assert.deepStrictEqual(inspect(parser(input(' a ', ctx)), ctx), [['a '], '']);
17
+ assert.deepStrictEqual(inspect(parser(input(' a \n', ctx)), ctx), [['a '], '']);
18
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
19
+ assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\na'], '']);
20
+ assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\n a'], '']);
21
+ assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a'], ' a']);
22
+ assert.deepStrictEqual(inspect(parser(input(' \ta', ctx)), ctx), [['\ta'], '']);
23
+ assert.deepStrictEqual(inspect(parser(input('\ta', ctx)), ctx), [['a'], '']);
21
24
  });
22
25
 
23
26
  });
@@ -1,4 +1,4 @@
1
- import { Parser, eval, exec } from '../../data/parser';
1
+ import { Parser, input, eval, failsafe } from '../../data/parser';
2
2
  import { some } from '../../data/parser/some';
3
3
  import { block } from '../constraint/block';
4
4
  import { line } from '../constraint/line';
@@ -16,24 +16,31 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
16
16
  opener = /^([ \t])\1*/;
17
17
  }
18
18
  assert(parser);
19
- return bind(block(match(
19
+ return failsafe(bind(block(match(
20
20
  opener,
21
21
  memoize(
22
22
  ([indent]) =>
23
- some(line(open(indent, ({ source }) => [[source], '']))),
23
+ some(line(open(indent, ({ context }) => {
24
+ const { source, position } = context;
25
+ context.position = source.length;
26
+ return [[source.slice(position)]];
27
+ }))),
24
28
  ([indent]) => indent.length * 2 + +(indent[0] === ' '), {})), separation),
25
- (lines, rest, context) => {
29
+ (lines, context) => {
30
+ const { source, position } = context;
26
31
  assert(parser = parser as Parser<N>);
27
32
  // 影響する使用はないはず
28
33
  //const { backtracks } = context;
29
34
  //context.backtracks = {};
30
- const result = parser({ source: trimBlockEnd(lines.join('')), context });
35
+ const result = parser(input(trimBlockEnd(lines.join('')), context));
31
36
  //context.backtracks = backtracks;
32
37
  assert(result);
33
- return result && exec(result) === ''
34
- ? [eval(result), rest]
38
+ context.position = position - (context.source.length - context.position);
39
+ context.source = source;
40
+ return result && context.position === position
41
+ ? [eval(result)]
35
42
  : undefined;
36
- });
43
+ }));
37
44
  }
38
45
 
39
46
  function trimBlockEnd(block: string): string {
@@ -1,21 +1,22 @@
1
- import { Parser, exec, check } from '../../data/parser';
1
+ import { Parser, failsafe } from '../../data/parser';
2
2
  import { consume } from '../../../combinator';
3
3
 
4
4
  export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P, cost?: boolean): P;
5
5
  export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>, cost = false): Parser<N> {
6
6
  assert(!pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^'));
7
- return input => {
8
- const { source, context } = input;
9
- if (source === '') return;
10
- const param = source.match(pattern);
7
+ return failsafe(input => {
8
+ const { context } = input;
9
+ const { source, position } = context;
10
+ if (position === source.length) return;
11
+ const param = source.slice(position).match(pattern);
11
12
  if (!param) return;
12
- assert(source.startsWith(param[0]));
13
+ assert(source.startsWith(param[0], position));
13
14
  cost && consume(param[0].length, context);
14
15
  const result = f(param)(input);
15
- assert(check(source, result, false));
16
- if (result === undefined) return;
17
- return exec(result).length < source.length && exec(result).length <= source.length
16
+ context.position += result && context.position === position ? param[0].length : 0;
17
+ assert(context.position > position || !result);
18
+ return context.position > position
18
19
  ? result
19
20
  : undefined;
20
- };
21
+ });
21
22
  }
@@ -1,13 +1,17 @@
1
- import { Parser, Input, Result, Node, Context } from '../../data/parser';
1
+ import { Parser, Input, Result, Ctx, Node, Context } from '../../data/parser';
2
2
 
3
3
  export function recover<P extends Parser<unknown>>(parser: P, fallback: (input: Input<Context<P>>, reason: unknown) => Result<Node<P>>): P;
4
- export function recover<N>(parser: Parser<N>, fallback: (input: Input, reason: unknown) => Result<N>): Parser<N> {
4
+ export function recover<N>(parser: Parser<N>, fallback: (input: Input<Ctx>, reason: unknown) => Result<N>): Parser<N> {
5
5
  return input => {
6
+ const { context } = input;
7
+ const { source, position } = context;
6
8
  try {
7
9
  return parser(input);
8
10
  }
9
11
  catch (reason) {
10
12
  assert(reason instanceof Error && reason.name === 'AssertionError' && !+console.error(reason) && eval(`throw new Error("${reason.name}")`) || 1);
13
+ context.source = source;
14
+ context.position = position;
11
15
  return fallback(input, reason);
12
16
  }
13
17
  };