securemark 0.293.2 → 0.293.4

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 (45) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +7 -10
  3. package/dist/index.js +349 -167
  4. package/package.json +1 -1
  5. package/src/combinator/control/manipulation/indent.test.ts +6 -1
  6. package/src/combinator/control/manipulation/indent.ts +1 -1
  7. package/src/combinator/control/manipulation/surround.ts +2 -1
  8. package/src/combinator/data/parser/context/delimiter.ts +7 -12
  9. package/src/combinator/data/parser/some.ts +4 -8
  10. package/src/combinator/data/parser.ts +0 -3
  11. package/src/parser/api/bind.ts +1 -1
  12. package/src/parser/api/parse.ts +1 -1
  13. package/src/parser/block/dlist.test.ts +1 -1
  14. package/src/parser/block/heading.test.ts +1 -0
  15. package/src/parser/block/olist.test.ts +9 -6
  16. package/src/parser/block/olist.ts +2 -2
  17. package/src/parser/block/ulist.test.ts +1 -0
  18. package/src/parser/block.ts +38 -36
  19. package/src/parser/inline/annotation.test.ts +1 -1
  20. package/src/parser/inline/bracket.test.ts +4 -2
  21. package/src/parser/inline/bracket.ts +114 -88
  22. package/src/parser/inline/emphasis.test.ts +1 -0
  23. package/src/parser/inline/html.test.ts +3 -3
  24. package/src/parser/inline/html.ts +32 -21
  25. package/src/parser/inline/italic.test.ts +1 -0
  26. package/src/parser/inline/link.test.ts +10 -8
  27. package/src/parser/inline/link.ts +18 -18
  28. package/src/parser/inline/mark.test.ts +1 -0
  29. package/src/parser/inline/math.ts +2 -2
  30. package/src/parser/inline/media.test.ts +6 -7
  31. package/src/parser/inline/media.ts +6 -5
  32. package/src/parser/inline/reference.test.ts +1 -1
  33. package/src/parser/inline/remark.test.ts +3 -1
  34. package/src/parser/inline/remark.ts +2 -2
  35. package/src/parser/inline/strong.test.ts +1 -0
  36. package/src/parser/inline/template.ts +1 -1
  37. package/src/parser/inline.test.ts +16 -16
  38. package/src/parser/inline.ts +46 -47
  39. package/src/parser/segment.ts +12 -12
  40. package/src/parser/source/escapable.test.ts +1 -0
  41. package/src/parser/source/escapable.ts +3 -9
  42. package/src/parser/source/text.test.ts +5 -4
  43. package/src/parser/source/text.ts +175 -24
  44. package/src/parser/source/unescapable.test.ts +1 -0
  45. package/src/parser/source/unescapable.ts +2 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.293.2",
3
+ "version": "0.293.4",
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",
@@ -12,12 +12,17 @@ describe('Unit: combinator/indent', () => {
12
12
  assert.deepStrictEqual(inspect(parser(input(' ', ctx)), ctx), undefined);
13
13
  assert.deepStrictEqual(inspect(parser(input(' ', ctx)), ctx), undefined);
14
14
  assert.deepStrictEqual(inspect(parser(input('a ', ctx)), ctx), undefined);
15
- assert.deepStrictEqual(inspect(parser(input(' a\n', ctx)), ctx), [['a'], '']);
15
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
16
16
  assert.deepStrictEqual(inspect(parser(input(' a ', ctx)), ctx), [['a '], '']);
17
+ assert.deepStrictEqual(inspect(parser(input(' a\n', ctx)), ctx), [['a'], '']);
17
18
  assert.deepStrictEqual(inspect(parser(input(' a \n', ctx)), ctx), [['a '], '']);
18
19
  assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
20
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
21
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
22
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [[' a'], '']);
19
23
  assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\na'], '']);
20
24
  assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\n a'], '']);
25
+ assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\n a'], '']);
21
26
  assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a'], ' a']);
22
27
  assert.deepStrictEqual(inspect(parser(input(' \ta', ctx)), ctx), [['\ta'], '']);
23
28
  assert.deepStrictEqual(inspect(parser(input('\ta', ctx)), ctx), [['a'], '']);
@@ -13,7 +13,7 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
13
13
  if (typeof opener === 'function') {
14
14
  separation = parser as boolean;
15
15
  parser = opener;
16
- opener = /([ \t])\1*/y;
16
+ opener = / {1,4}|\t{1,2}/y;
17
17
  }
18
18
  assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^'));
19
19
  assert(parser);
@@ -157,7 +157,8 @@ export function setBacktrack(
157
157
  position: number,
158
158
  length: number = 1,
159
159
  ): void {
160
- const { source } = context;
160
+ const { source, state = 0 } = context;
161
+ if (state === 0) return;
161
162
  if (position === source.length) return;
162
163
  if (length === 0) return;
163
164
  for (const backtrack of backtracks) {
@@ -7,26 +7,24 @@ interface Delimiter {
7
7
  readonly signature: number | string;
8
8
  readonly matcher: (input: Input) => boolean | undefined;
9
9
  readonly precedence: number;
10
- readonly linebreakable: boolean;
11
10
  state: boolean;
12
11
  }
13
12
 
14
13
  export class Delimiters {
15
14
  // 手間を惜しまなければ規定のパターンはすべて配列のインデクスに変換可能。
16
- public static signature(pattern: string | RegExp | undefined, linebreakable: boolean): number | string {
15
+ public static signature(pattern: string | RegExp | undefined): number | string {
17
16
  switch (typeof pattern) {
18
17
  case 'undefined':
19
- return +linebreakable;
18
+ return 1 << 7;
20
19
  case 'string':
21
20
  assert(pattern !== '\x00');
22
21
  if (pattern.length === 1) {
23
22
  const code = pattern.charCodeAt(0);
24
- // 使用中のパターンの8ビット目が空いてるのでひとまずこうしとく
25
- if ((code & 1 << 7) === 0) return code | +linebreakable << 7;
23
+ return code;
26
24
  }
27
- return `s:${pattern}:${+linebreakable}`;
25
+ return `s:${pattern}`;
28
26
  case 'object':
29
- return `r/${pattern.source}/${+linebreakable}`;
27
+ return `r/${pattern.source}`;
30
28
  }
31
29
  }
32
30
  public static matcher(pattern: string | RegExp | undefined): (input: Input<Ctx>) => true | undefined {
@@ -61,14 +59,13 @@ export class Delimiters {
61
59
  readonly signature: number | string;
62
60
  readonly matcher: (input: Input) => boolean | undefined;
63
61
  readonly precedence: number;
64
- readonly linebreakable: boolean;
65
62
  }[]
66
63
  ): void {
67
64
  const { delimiters, stack } = this;
68
65
  // シグネチャ数以下
69
66
  assert(delimiters.length < 100);
70
67
  for (let i = 0; i < delims.length; ++i) {
71
- const { signature, matcher, precedence, linebreakable } = delims[i];
68
+ const { signature, matcher, precedence } = delims[i];
72
69
  const memory = this.registry(signature);
73
70
  const index = memory[0]?.index ?? delimiters.length;
74
71
  assert(memory.length === 0 || precedence === delimiters[index].precedence);
@@ -79,7 +76,6 @@ export class Delimiters {
79
76
  signature,
80
77
  matcher,
81
78
  precedence,
82
- linebreakable,
83
79
  state: true,
84
80
  };
85
81
  delimiters[index] = delimiter;
@@ -134,14 +130,13 @@ export class Delimiters {
134
130
  }
135
131
  }
136
132
  public match(input: Input): boolean {
137
- const { precedence = 0, linebreak = 0 } = input.context;
133
+ const { precedence = 0 } = input.context;
138
134
  const { delimiters } = this;
139
135
  for (let i = delimiters.length; i--;) {
140
136
  const delimiter = delimiters[i];
141
137
  if (delimiter.precedence <= precedence || !delimiter.state) continue;
142
138
  switch (delimiter.matcher(input)) {
143
139
  case true:
144
- if (!delimiter.linebreakable && linebreak > 0) return false;
145
140
  return true;
146
141
  case false:
147
142
  return false;
@@ -1,8 +1,7 @@
1
1
  import { Parser, eval } from '../parser';
2
2
  import { Delimiters } from './context/delimiter';
3
- import { unshift, push } from 'spica/array';
4
3
 
5
- type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number, linebreak?: boolean];
4
+ type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
6
5
 
7
6
  export function some<P extends Parser<unknown>>(parser: P, limit?: number): P;
8
7
  export function some<P extends Parser<unknown>>(parser: P, end?: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
@@ -10,11 +9,10 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
10
9
  if (typeof end === 'number') return some(parser, undefined, delimiters, end);
11
10
  assert(parser);
12
11
  const match = Delimiters.matcher(end);
13
- const delims = delimiters.map(([delimiter, precedence, linebreakable = true]) => ({
14
- signature: Delimiters.signature(delimiter, linebreakable),
12
+ const delims = delimiters.map(([delimiter, precedence]) => ({
13
+ signature: Delimiters.signature(delimiter),
15
14
  matcher: Delimiters.matcher(delimiter),
16
15
  precedence,
17
- linebreakable,
18
16
  }));
19
17
  return input => {
20
18
  const { context } = input;
@@ -32,9 +30,7 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
32
30
  const result = parser(input);
33
31
  if (result === undefined) break;
34
32
  nodes = nodes
35
- ? nodes.length < eval(result).length / 8
36
- ? unshift(nodes, eval(result))
37
- : push(nodes, eval(result))
33
+ ? (nodes.push(...eval(result)), nodes)
38
34
  : eval(result);
39
35
  if (limit >= 0 && context.position - position > limit) break;
40
36
  }
@@ -1,5 +1,4 @@
1
1
  import { Delimiters } from './parser/context/delimiter';
2
- import { MarkdownParser } from '../../../markdown';
3
2
 
4
3
  export type Parser<N, C extends CtxOptions = CtxOptions, D extends Parser<unknown, C>[] = any>
5
4
  = (input: Input<C & Ctx>) => Result<N, C, D>;
@@ -38,8 +37,6 @@ export type IntermediateParser<P extends Parser<unknown>> = Parser<SubNode<P>, C
38
37
  type ExtractSubNode<D extends Parser<unknown>[]> = ExtractSubParser<D> extends infer N ? N extends Parser<infer U> ? U : never : never;
39
38
  type ExtractSubParser<D extends Parser<unknown>[]> = D extends (infer P)[] ? P extends Parser<unknown> ? P : never : never;
40
39
 
41
- export function input(source: string, context: CtxOptions): Input<Ctx>;
42
- export function input(source: string, context: MarkdownParser.Options): Input<MarkdownParser.Context>;
43
40
  export function input(source: string, context: CtxOptions): Input<Ctx> {
44
41
  // @ts-expect-error
45
42
  context.source = source;
@@ -78,7 +78,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
78
78
  for (; index < sourceSegments.length - last; ++index) {
79
79
  assert(rev === revision);
80
80
  const seg = sourceSegments[index];
81
- const es = eval(header(input(seg, { header: index === 0 })) || block(input(seg, context)), []);
81
+ const es = eval(header(input(seg, { header: index === 0 } as MarkdownParser.Context)) || block(input(seg, context)), []);
82
82
  blocks.splice(index, 0, [seg, es, url]);
83
83
  if (es.length === 0) continue;
84
84
  // All deletion processes always run after all addition processes have done.
@@ -34,7 +34,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
34
34
  const node = frag();
35
35
  let index = 0;
36
36
  for (const seg of segment(source)) {
37
- node.append(...eval(header(input(seg, { header: index++ === 0 })) || block(input(seg, context)), []));
37
+ node.append(...eval(header(input(seg, { header: index++ === 0 } as MarkdownParser.Context)) || block(input(seg, context)), []));
38
38
  }
39
39
  assert(opts.id !== '' || !node.querySelector('[id], .index[href], .label[href], .annotation > a[href], .reference > a[href]'));
40
40
  if (opts.test) return node;
@@ -69,7 +69,7 @@ describe('Unit: parser/block/dlist', () => {
69
69
  it('indexer', () => {
70
70
  assert.deepStrictEqual(inspect(parser('~ a [|b]'), ctx), [['<dl><dt id="index::b">a<span class="indexer" data-index="b"></span></dt><dd></dd></dl>'], '']);
71
71
  assert.deepStrictEqual(inspect(parser('~ a [|b]\\'), ctx), [['<dl><dt id="index::a_[|b]">a <span class="invalid">[|b]</span></dt><dd></dd></dl>'], '']);
72
- assert.deepStrictEqual(inspect(parser('~ A'), ctx), [['<dl><dt id="index::A">A</dt><dd></dd></dl>'], '']);
72
+ assert.deepStrictEqual(inspect(parser('~ - [|b]'), ctx), [['<dl><dt id="index::b">-<span class="indexer" data-index="b"></span></dt><dd></dd></dl>'], '']);
73
73
  assert.deepStrictEqual(inspect(parser('~ *A*'), ctx), [['<dl><dt id="index::A"><em>A</em></dt><dd></dd></dl>'], '']);
74
74
  assert.deepStrictEqual(inspect(parser('~ `A`'), ctx), [['<dl><dt id="index::`A`"><code data-src="`A`">A</code></dt><dd></dd></dl>'], '']);
75
75
  assert.deepStrictEqual(inspect(parser('~ ${A}$'), ctx), [['<dl><dt id="index::${A}$"><span class="math" translate="no" data-src="${A}$">${A}$</span></dt><dd></dd></dl>'], '']);
@@ -79,6 +79,7 @@ describe('Unit: parser/block/heading', () => {
79
79
  assert.deepStrictEqual(inspect(parser('# a [|b] [|c]'), ctx), [['<h1 id="index::c">a <span class="invalid">[|b]</span><span class="indexer" data-index="c"></span></h1>'], '']);
80
80
  assert.deepStrictEqual(inspect(parser('# a [|b] \n'), ctx), [['<h1 id="index::b">a<span class="indexer" data-index="b"></span></h1>'], '']);
81
81
  assert.deepStrictEqual(inspect(parser('# a \\[|b]'), ctx), [['<h1 id="index::a_[|b]">a [|b]</h1>'], '']);
82
+ assert.deepStrictEqual(inspect(parser('# - [|b]'), ctx), [['<h1 id="index::b">-<span class="indexer" data-index="b"></span></h1>'], '']);
82
83
  assert.deepStrictEqual(inspect(parser('## a [|b] [|c]'), ctx), [['<h2 id="index::c">a <span class="invalid">[|b]</span><span class="indexer" data-index="c"></span></h2>'], '']);
83
84
  });
84
85
 
@@ -32,6 +32,8 @@ describe('Unit: parser/block/olist', () => {
32
32
  assert.deepStrictEqual(inspect(parser('(1)'), ctx), undefined);
33
33
  assert.deepStrictEqual(inspect(parser('(1)\n'), ctx), undefined);
34
34
  assert.deepStrictEqual(inspect(parser('(1)\n(1) a'), ctx), undefined);
35
+ assert.deepStrictEqual(inspect(parser('I. '), ctx), undefined);
36
+ assert.deepStrictEqual(inspect(parser('A. '), ctx), undefined);
35
37
  assert.deepStrictEqual(inspect(parser('(I) '), ctx), undefined);
36
38
  assert.deepStrictEqual(inspect(parser('(A) '), ctx), undefined);
37
39
  assert.deepStrictEqual(inspect(parser(' 1. '), ctx), undefined);
@@ -117,12 +119,12 @@ describe('Unit: parser/block/olist', () => {
117
119
  });
118
120
 
119
121
  it('type', () => {
120
- assert.deepStrictEqual(inspect(parser('i. '), ctx), [['<ol type="i" data-type="lower-roman"><li></li></ol>'], '']);
121
- assert.deepStrictEqual(inspect(parser('a. '), ctx), [['<ol type="a" data-type="lower-alpha"><li></li></ol>'], '']);
122
- assert.deepStrictEqual(inspect(parser('I. '), ctx), [['<ol type="I" data-type="upper-roman"><li></li></ol>'], '']);
123
- assert.deepStrictEqual(inspect(parser('A. '), ctx), [['<ol type="A" data-type="upper-alpha"><li></li></ol>'], '']);
124
- assert.deepStrictEqual(inspect(parser('a. \n1.\nc'), ctx), [['<ol type="a" data-type="lower-alpha"><li></li><li data-marker="1."></li><li data-marker="c."></li></ol>'], '']);
125
- assert.deepStrictEqual(inspect(parser('i. 1'), ctx), [['<ol type="i" data-type="lower-roman"><li id="index::1">1</li></ol>'], '']);
122
+ assert.deepStrictEqual(inspect(parser('1. \n i. '), ctx), [['<ol><li><br><ol type="i" data-type="lower-roman"><li></li></ol></li></ol>'], '']);
123
+ assert.deepStrictEqual(inspect(parser('1. \n a. '), ctx), [['<ol><li><br><ol type="a" data-type="lower-alpha"><li></li></ol></li></ol>'], '']);
124
+ assert.deepStrictEqual(inspect(parser('1. \n I. '), ctx), [['<ol><li><br><ol type="I" data-type="upper-roman"><li></li></ol></li></ol>'], '']);
125
+ assert.deepStrictEqual(inspect(parser('1. \n A. '), ctx), [['<ol><li><br><ol type="A" data-type="upper-alpha"><li></li></ol></li></ol>'], '']);
126
+ assert.deepStrictEqual(inspect(parser('1. \n a. \n 1.\n c'), ctx), [['<ol><li><br><ol type="a" data-type="lower-alpha"><li></li><li data-marker="1."></li><li data-marker="c."></li></ol></li></ol>'], '']);
127
+ assert.deepStrictEqual(inspect(parser('1. \n i. 1'), ctx), [['<ol><li><br><ol type="i" data-type="lower-roman"><li id="index::1">1</li></ol></li></ol>'], '']);
126
128
  });
127
129
 
128
130
  it('checkbox', () => {
@@ -136,6 +138,7 @@ describe('Unit: parser/block/olist', () => {
136
138
  assert.deepStrictEqual(inspect(parser('1. [|a]'), ctx), [['<ol><li id="index::[|a]"><span class="invalid">[|a]</span></li></ol>'], '']);
137
139
  assert.deepStrictEqual(inspect(parser('1. a [|]'), ctx), [['<ol><li>a<span class="indexer" data-index=""></span></li></ol>'], '']);
138
140
  assert.deepStrictEqual(inspect(parser('1. a [|b]'), ctx), [['<ol><li id="index::b">a<span class="indexer" data-index="b"></span></li></ol>'], '']);
141
+ assert.deepStrictEqual(inspect(parser('1. - [|]'), ctx), [['<ol><li>-<span class="indexer" data-index=""></span></li></ol>'], '']);
139
142
  assert.deepStrictEqual(inspect(parser('1. [ ] [|a]'), ctx), [['<ol class="checklist"><li id="index::[|a]"><span class="checkbox">☐</span><span class="invalid">[|a]</span></li></ol>'], '']);
140
143
  assert.deepStrictEqual(inspect(parser('1. [ ] a [|b]'), ctx), [['<ol class="checklist"><li id="index::b"><span class="checkbox">☐</span>a<span class="indexer" data-index="b"></span></li></ol>'], '']);
141
144
  assert.deepStrictEqual(inspect(parser('1. a [|]\n 1. c [|d]'), ctx), [['<ol><li>a<span class="indexer" data-index=""></span><ol><li id="index::d">c<span class="indexer" data-index="d"></span></li></ol></li></ol>'], '']);
@@ -16,8 +16,8 @@ const openers = {
16
16
 
17
17
  export const olist: OListParser = lazy(() => block(validate(
18
18
  new RegExp([
19
- /([0-9]+|[a-z]+|[A-Z]+)(?:-[0-9]+)*\. /y.source,
20
- /\(([0-9]+|[a-z]+)\)(?:-[0-9]+)* /y.source,
19
+ /(?:[0-9]+)(?:-[0-9]+)*\. /y.source,
20
+ /\((?:[0-9]+)\)(?:-[0-9]+)* /y.source,
21
21
  ].join('|'), 'y'),
22
22
  olist_)));
23
23
 
@@ -75,6 +75,7 @@ describe('Unit: parser/block/ulist', () => {
75
75
  assert.deepStrictEqual(inspect(parser('- [|a]'), ctx), [['<ul><li id="index::[|a]"><span class="invalid">[|a]</span></li></ul>'], '']);
76
76
  assert.deepStrictEqual(inspect(parser('- a [|]'), ctx), [['<ul><li>a<span class="indexer" data-index=""></span></li></ul>'], '']);
77
77
  assert.deepStrictEqual(inspect(parser('- a [|b]'), ctx), [['<ul><li id="index::b">a<span class="indexer" data-index="b"></span></li></ul>'], '']);
78
+ assert.deepStrictEqual(inspect(parser('- - [|b]'), ctx), [['<ul><li id="index::b">-<span class="indexer" data-index="b"></span></li></ul>'], '']);
78
79
  assert.deepStrictEqual(inspect(parser('- [ ] [|a]'), ctx), [['<ul class="checklist"><li id="index::[|a]"><span class="checkbox">☐</span><span class="invalid">[|a]</span></li></ul>'], '']);
79
80
  assert.deepStrictEqual(inspect(parser('- [ ] a [|b]'), ctx), [['<ul class="checklist"><li id="index::b"><span class="checkbox">☐</span>a<span class="indexer" data-index="b"></span></li></ul>'], '']);
80
81
  assert.deepStrictEqual(inspect(parser('- a [|]\n - c [|d]'), ctx), [['<ul><li>a<span class="indexer" data-index=""></span><ul><li id="index::d">c<span class="indexer" data-index="d"></span></li></ul></li></ul>'], '']);
@@ -58,51 +58,53 @@ export const block: BlockParser = reset(
58
58
  input => {
59
59
  const { context: { source, position } } = input;
60
60
  if (position === source.length) return;
61
- switch (source.slice(position, position + 3)) {
62
- case '===':
63
- return pagebreak(input);
64
- case '~~~':
65
- return extension(input);
66
- case '```':
67
- return codeblock(input);
68
- }
69
- switch (source.slice(position, position + 2)) {
70
- case '$$':
71
- return mathblock(input);
72
- case '[$':
73
- return extension(input);
74
- case '[!':
61
+ const fst = source[position];
62
+ switch (fst) {
63
+ case '=':
64
+ if (source.startsWith('===', position)) return pagebreak(input);
65
+ break;
66
+ case '`':
67
+ if (source.startsWith('```', position)) return codeblock(input);
68
+ break;
69
+ case '~':
70
+ if (source.startsWith('~~~', position)) return extension(input);
71
+ if (source[position + 1] === ' ') return dlist(input);
72
+ break;
73
+ case '-':
74
+ if (source[position + 1] === ' ') return ulist(input) || ilist(input);
75
+ break;
76
+ case '+':
77
+ case '*':
78
+ if (source[position + 1] === ' ') return ilist(input);
79
+ break;
80
+ case '[':
81
+ switch (source[position + 1]) {
82
+ case '$':
83
+ return extension(input);
84
+ case '!':
85
+ return mediablock(input);
86
+ }
87
+ break;
88
+ case '!':
89
+ if (source[position + 1] === '>') return blockquote(input);
75
90
  return mediablock(input);
76
- case '!>':
91
+ case '>':
92
+ if (source[position + 1] === '>') return blockquote(input) || reply(input);
77
93
  return blockquote(input);
78
- case '>>':
79
- return blockquote(input)
80
- || reply(input);
81
- case '- ':
82
- return ulist(input)
83
- || ilist(input);
84
- case '+ ':
85
- case '* ':
86
- return ilist(input);
87
- case '~ ':
88
- return dlist(input);
89
- }
90
- switch (source[position]) {
91
94
  case '#':
92
95
  return heading(input);
93
- case '|':
94
- return table(input)
95
- || sidefence(input);
96
96
  case '$':
97
+ if (source[position + 1] === '$') return mathblock(input);
97
98
  return extension(input);
98
- case '>':
99
- return blockquote(input);
100
- case '!':
101
- return mediablock(input);
99
+ case '|':
100
+ return table(input) || sidefence(input);
101
+ case '(':
102
+ return olist(input);
103
+ default:
104
+ if ('0' <= fst && fst <= '9') return olist(input);
102
105
  }
103
106
  },
104
107
  emptyline,
105
- olist,
106
108
  paragraph
107
109
  ]) as any));
108
110
 
@@ -17,7 +17,7 @@ describe('Unit: parser/inline/annotation', () => {
17
17
  assert.deepStrictEqual(inspect(parser('(()))'), ctx), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('(("))'), ctx), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('(([))'), ctx), undefined);
20
- assert.deepStrictEqual(inspect(parser('((<bdi>))'), ctx), undefined);
20
+ assert.deepStrictEqual(inspect(parser('(([%))'), ctx), undefined);
21
21
  assert.deepStrictEqual(inspect(parser('(( ))'), ctx), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('(( (a'), ctx), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('((\n))'), ctx), undefined);
@@ -41,6 +41,7 @@ describe('Unit: parser/inline/bracket', () => {
41
41
  assert.deepStrictEqual(inspect(parser('(ABBR, ABBR)'), ctx), [['(', 'ABBR, ABBR', ')'], '']);
42
42
  assert.deepStrictEqual(inspect(parser('(\\a)'), ctx), [['<span class="paren">(a)</span>'], '']);
43
43
  assert.deepStrictEqual(inspect(parser('(==)'), ctx), [['<span class="paren">(==)</span>'], '']);
44
+ assert.deepStrictEqual(inspect(parser('("(\n))"(")'), ctx), [['<span class="paren">("(<br>)</span>'], ')"(")']);
44
45
  assert.deepStrictEqual(inspect(parser('($)$'), ctx), [['(', '<span class="math" translate="no" data-src="$)$">$)$</span>'], '']);
45
46
  assert.deepStrictEqual(inspect(parser(')'), ctx), undefined);
46
47
  assert.deepStrictEqual(inspect(parser('(1,2)'), ctx), [['(', '1,2', ')'], '']);
@@ -82,8 +83,9 @@ describe('Unit: parser/inline/bracket', () => {
82
83
  assert.deepStrictEqual(inspect(parser('"(")"'), ctx), [['"', '(', '"'], ')"']);
83
84
  assert.deepStrictEqual(inspect(parser('"(("'), ctx), [['"', '(', '(', '"'], '']);
84
85
  assert.deepStrictEqual(inspect(parser('"(\\")"'), ctx), [['"', '<span class="paren">(")</span>', '"'], '']);
85
- assert.deepStrictEqual(inspect(parser('"\n"'), ctx), [['"', '<br>', '"'], '']);
86
- assert.deepStrictEqual(inspect(parser('"\n"(")'), ctx), [['"', '<br>', '"', '(', '"'], ')']);
86
+ assert.deepStrictEqual(inspect(parser('"(\n)"(")'), ctx), [['"', '('], '\n)"(")']);
87
+ assert.deepStrictEqual(inspect(parser('"\n"'), ctx), [['"'], '\n"']);
88
+ assert.deepStrictEqual(inspect(parser('"\n"(")'), ctx), [['"'], '\n"(")']);
87
89
  });
88
90
 
89
91
  });
@@ -1,6 +1,6 @@
1
1
  import { BracketParser } from '../inline';
2
2
  import { State, Recursion, Backtrack } from '../context';
3
- import { union, some, recursion, precedence, validate, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
3
+ import { union, some, recursion, precedence, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { textlink } from './link';
6
6
  import { str } from '../source';
@@ -11,93 +11,119 @@ const indexA = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*$/;
11
11
  const indexF = new RegExp(indexA.source.replace(', ', '[,、]')
12
12
  .replace(/[09AZaz.]|\-(?!\w)/g, c => String.fromCodePoint(c.codePointAt(0)! + 0xFEE0)));
13
13
 
14
- export const bracket: BracketParser = lazy(() => validate(/[([{([{"]/y, union([
15
- surround(
16
- str('('),
17
- precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
18
- str(')'),
19
- true,
20
- ([as, bs = [], cs], { source, position, range = 0 }) => {
21
- const str = source.slice(position - range + 1, position - 1);
22
- return indexA.test(str)
23
- ? [[as[0], str, cs[0]]]
24
- : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
25
- },
26
- ([as, bs = []]) => [unshift(as, bs)],
27
- [2 | Backtrack.bracket]),
28
- surround(
29
- str('('),
30
- precedence(1, recursion(Recursion.bracket, some(inline, '', [[')', 1]]))),
31
- str(')'),
32
- true,
33
- ([as, bs = [], cs], { source, position, range = 0 }) => {
34
- const str = source.slice(position - range + 1, position - 1);
35
- return indexF.test(str)
36
- ? [[as[0], str, cs[0]]]
37
- : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
38
- },
39
- ([as, bs = []]) => [unshift(as, bs)]),
40
- surround(
41
- str('['),
42
- precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
43
- str(']'),
44
- true,
45
- ([as, bs = [], cs], context) => {
46
- if (context.state! & State.link) {
47
- const { source, position, range = 0 } = context;
48
- const head = position - range;
49
- if (context.linebreak !== 0 || source[position] !== '{') {
14
+ export const bracket: BracketParser = lazy(() => union([
15
+ input => {
16
+ const { context: { source, position } } = input;
17
+ switch (source[position]) {
18
+ case '(':
19
+ return p1(input);
20
+ case '(':
21
+ return p2(input);
22
+ case '[':
23
+ return s1(input);
24
+ case '':
25
+ return s2(input);
26
+ case '{':
27
+ return c1(input);
28
+ case '{':
29
+ return c2(input);
30
+ case '"':
31
+ return d1(input);
32
+ }
33
+ }
34
+ ])) as any;
35
+
36
+ const p1 = lazy(() => surround(
37
+ str('('),
38
+ precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
39
+ str(')'),
40
+ true,
41
+ ([as, bs = [], cs], { source, position, range = 0 }) => {
42
+ const str = source.slice(position - range + 1, position - 1);
43
+ return indexA.test(str)
44
+ ? [[as[0], str, cs[0]]]
45
+ : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
46
+ },
47
+ ([as, bs = []]) => [unshift(as, bs)],
48
+ [2 | Backtrack.bracket]));
49
+
50
+ const p2 = lazy(() => surround(
51
+ str('('),
52
+ precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
53
+ str(')'),
54
+ true,
55
+ ([as, bs = [], cs], { source, position, range = 0 }) => {
56
+ const str = source.slice(position - range + 1, position - 1);
57
+ return indexF.test(str)
58
+ ? [[as[0], str, cs[0]]]
59
+ : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
60
+ },
61
+ ([as, bs = []]) => [unshift(as, bs)],
62
+ [2 | Backtrack.bracket]));
63
+
64
+ const s1 = lazy(() => surround(
65
+ str('['),
66
+ precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
67
+ str(']'),
68
+ true,
69
+ ([as, bs = [], cs], context) => {
70
+ if (context.state! & State.link) {
71
+ const { source, position, range = 0 } = context;
72
+ const head = position - range;
73
+ if (context.linebreak !== 0 || source[position] !== '{') {
74
+ setBacktrack(context, [2 | Backtrack.link], head);
75
+ }
76
+ else {
77
+ context.state! ^= State.link;
78
+ const result = !isBacktrack(context, [1 | Backtrack.link])
79
+ ? textlink({ context })
80
+ : undefined;
81
+ context.position = position;
82
+ if (!result) {
50
83
  setBacktrack(context, [2 | Backtrack.link], head);
51
84
  }
52
- else {
53
- context.state! ^= State.link;
54
- const result = !isBacktrack(context, [1 | Backtrack.link])
55
- ? textlink({ context })
56
- : undefined;
57
- context.position = position;
58
- if (!result) {
59
- setBacktrack(context, [2 | Backtrack.link], head);
60
- }
61
- context.state! ^= State.link;
62
- context.range = range;
63
- }
85
+ context.state! ^= State.link;
86
+ context.range = range;
64
87
  }
65
- return [push(unshift(as, bs), cs)];
66
- },
67
- ([as, bs = []]) => [unshift(as, bs)],
68
- [2 | Backtrack.bracket]),
69
- surround(
70
- str('['),
71
- precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
72
- str(''),
73
- true,
74
- undefined,
75
- ([as, bs = []]) => [unshift(as, bs)]),
76
- surround(
77
- str('{'),
78
- precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
79
- str('}'),
80
- true,
81
- undefined,
82
- ([as, bs = []]) => [unshift(as, bs)],
83
- [2 | Backtrack.bracket]),
84
- surround(
85
- str('{'),
86
- precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
87
- str('}'),
88
- true,
89
- undefined,
90
- ([as, bs = []]) => [unshift(as, bs)]),
91
- // 同一行内でしか閉じない以外括弧と同じ挙動
92
- surround(
93
- str('"'),
94
- precedence(2, recursion(Recursion.bracket, some(inline, '"', [['"', 2, false]]))),
95
- str('"'),
96
- true,
97
- ([as, bs = [], cs], context) =>
98
- context.linebreak === 0
99
- ? [push(unshift(as, bs), cs)]
100
- : (context.position -= 1, [unshift(as, bs)]),
101
- ([as, bs = []]) => [unshift(as, bs)],
102
- [2 | Backtrack.bracket]),
103
- ])));
88
+ }
89
+ return [push(unshift(as, bs), cs)];
90
+ },
91
+ ([as, bs = []]) => [unshift(as, bs)],
92
+ [2 | Backtrack.bracket]));
93
+
94
+ const s2 = lazy(() => surround(
95
+ str(''),
96
+ precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
97
+ str(']'),
98
+ true,
99
+ undefined,
100
+ ([as, bs = []]) => [unshift(as, bs)],
101
+ [2 | Backtrack.bracket]));
102
+
103
+ const c1 = lazy(() => surround(
104
+ str('{'),
105
+ precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
106
+ str('}'),
107
+ true,
108
+ undefined,
109
+ ([as, bs = []]) => [unshift(as, bs)],
110
+ [2 | Backtrack.bracket]));
111
+
112
+ const c2 = lazy(() => surround(
113
+ str('{'),
114
+ precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
115
+ str('}'),
116
+ true,
117
+ undefined,
118
+ ([as, bs = []]) => [unshift(as, bs)],
119
+ [2 | Backtrack.bracket]));
120
+
121
+ const d1 = lazy(() => surround(
122
+ str('"'),
123
+ // 改行の優先度を構文ごとに変える場合シグネチャの優先度対応が必要
124
+ precedence(2, recursion(Recursion.bracket, some(inline, /["\n]/y, [['"', 2], ['\n', 3]]))),
125
+ str('"'),
126
+ true,
127
+ undefined,
128
+ ([as, bs = []]) => [unshift(as, bs)],
129
+ [2 | Backtrack.bracket]));
@@ -40,6 +40,7 @@ describe('Unit: parser/inline/emphasis', () => {
40
40
 
41
41
  it('nest', () => {
42
42
  assert.deepStrictEqual(inspect(parser('*a *b**'), ctx), [['<em>a <em>b</em></em>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('*- *b**'), ctx), [['<em>- <em>b</em></em>'], '']);
43
44
  assert.deepStrictEqual(inspect(parser('*a **b***'), ctx), [['<em>a <strong>b</strong></em>'], '']);
44
45
  assert.deepStrictEqual(inspect(parser('*a\\ *b**'), ctx), [['<em>a <em>b</em></em>'], '']);
45
46
  assert.deepStrictEqual(inspect(parser('*a&Tab;*b**'), ctx), [['<em>a\t<em>b</em></em>'], '']);