securemark 0.257.1 → 0.258.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 (82) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +21 -8
  3. package/dist/index.js +1224 -611
  4. package/markdown.d.ts +1 -28
  5. package/package.json +9 -9
  6. package/src/combinator/control/manipulation/convert.ts +8 -4
  7. package/src/combinator/control/manipulation/scope.ts +10 -2
  8. package/src/combinator/data/parser/context/delimiter.ts +70 -0
  9. package/src/combinator/data/parser/context/memo.ts +30 -0
  10. package/src/combinator/{control/manipulation → data/parser}/context.test.ts +9 -9
  11. package/src/combinator/data/parser/context.ts +161 -0
  12. package/src/combinator/data/parser/inits.ts +1 -1
  13. package/src/combinator/data/parser/sequence.test.ts +1 -1
  14. package/src/combinator/data/parser/sequence.ts +1 -1
  15. package/src/combinator/data/parser/some.test.ts +1 -1
  16. package/src/combinator/data/parser/some.ts +14 -37
  17. package/src/combinator/data/parser/subsequence.test.ts +1 -1
  18. package/src/combinator/data/parser/union.test.ts +1 -1
  19. package/src/combinator/data/parser.ts +7 -47
  20. package/src/combinator.ts +1 -2
  21. package/src/parser/api/bind.ts +5 -5
  22. package/src/parser/api/parse.ts +3 -1
  23. package/src/parser/block/blockquote.ts +1 -1
  24. package/src/parser/block/dlist.ts +4 -10
  25. package/src/parser/block/extension/figure.ts +4 -3
  26. package/src/parser/block/extension/table.ts +2 -2
  27. package/src/parser/block/heading.ts +5 -13
  28. package/src/parser/block/ilist.ts +3 -2
  29. package/src/parser/block/olist.ts +10 -7
  30. package/src/parser/block/paragraph.ts +1 -1
  31. package/src/parser/block/reply/cite.ts +1 -1
  32. package/src/parser/block/reply/quote.ts +1 -1
  33. package/src/parser/block/reply.ts +1 -1
  34. package/src/parser/block/sidefence.ts +1 -1
  35. package/src/parser/block/table.test.ts +5 -0
  36. package/src/parser/block/table.ts +14 -13
  37. package/src/parser/block/ulist.ts +4 -3
  38. package/src/parser/block.ts +1 -1
  39. package/src/parser/context.ts +32 -0
  40. package/src/parser/header.ts +1 -1
  41. package/src/parser/inline/annotation.ts +9 -17
  42. package/src/parser/inline/autolink/email.ts +1 -1
  43. package/src/parser/inline/autolink/url.ts +2 -2
  44. package/src/parser/inline/autolink.ts +5 -3
  45. package/src/parser/inline/bracket.test.ts +2 -0
  46. package/src/parser/inline/bracket.ts +16 -15
  47. package/src/parser/inline/code.ts +1 -1
  48. package/src/parser/inline/comment.test.ts +1 -0
  49. package/src/parser/inline/comment.ts +4 -3
  50. package/src/parser/inline/deletion.ts +5 -4
  51. package/src/parser/inline/emphasis.ts +5 -4
  52. package/src/parser/inline/emstrong.ts +5 -4
  53. package/src/parser/inline/extension/index.ts +8 -15
  54. package/src/parser/inline/extension/indexee.ts +8 -10
  55. package/src/parser/inline/extension/indexer.ts +4 -3
  56. package/src/parser/inline/extension/label.ts +3 -2
  57. package/src/parser/inline/extension/placeholder.ts +6 -5
  58. package/src/parser/inline/html.ts +5 -4
  59. package/src/parser/inline/htmlentity.ts +1 -1
  60. package/src/parser/inline/insertion.ts +5 -4
  61. package/src/parser/inline/link.ts +12 -21
  62. package/src/parser/inline/mark.ts +5 -4
  63. package/src/parser/inline/math.ts +8 -9
  64. package/src/parser/inline/media.ts +8 -7
  65. package/src/parser/inline/reference.ts +12 -18
  66. package/src/parser/inline/ruby.ts +4 -3
  67. package/src/parser/inline/shortmedia.ts +3 -2
  68. package/src/parser/inline/strong.ts +5 -4
  69. package/src/parser/inline/template.test.ts +1 -1
  70. package/src/parser/inline/template.ts +9 -6
  71. package/src/parser/inline.test.ts +2 -0
  72. package/src/parser/locale.ts +6 -7
  73. package/src/parser/processor/footnote.ts +5 -3
  74. package/src/parser/source/text.ts +1 -1
  75. package/src/parser/util.ts +0 -220
  76. package/src/parser/visibility.ts +205 -0
  77. package/src/util/info.ts +4 -2
  78. package/src/util/quote.ts +12 -15
  79. package/src/util/toc.ts +14 -17
  80. package/webpack.config.js +1 -0
  81. package/src/combinator/control/manipulation/context.ts +0 -70
  82. package/src/combinator/control/manipulation/resource.ts +0 -54
package/src/combinator.ts CHANGED
@@ -4,14 +4,13 @@ export * from './combinator/data/parser/tails';
4
4
  export * from './combinator/data/parser/sequence';
5
5
  export * from './combinator/data/parser/subsequence';
6
6
  export * from './combinator/data/parser/some';
7
+ export * from './combinator/data/parser/context';
7
8
  export * from './combinator/control/constraint/block';
8
9
  export * from './combinator/control/constraint/line';
9
10
  export * from './combinator/control/constraint/contract';
10
11
  export * from './combinator/control/manipulation/fence';
11
12
  export * from './combinator/control/manipulation/indent';
12
13
  export * from './combinator/control/manipulation/scope';
13
- export * from './combinator/control/manipulation/context';
14
- export * from './combinator/control/manipulation/resource';
15
14
  export * from './combinator/control/manipulation/surround';
16
15
  export * from './combinator/control/manipulation/match';
17
16
  export * from './combinator/control/manipulation/convert';
@@ -2,9 +2,10 @@ import { undefined, location } from 'spica/global';
2
2
  import { ParserSettings, Progress } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
5
+ import { segment, validate, MAX_INPUT_SIZE } from '../segment';
5
6
  import { header } from '../header';
6
7
  import { block } from '../block';
7
- import { segment, validate, MAX_INPUT_SIZE } from '../segment';
8
+ import { backtrackable } from '../context';
8
9
  import { normalize } from './normalize';
9
10
  import { headers } from './header';
10
11
  import { figure } from '../processor/figure';
@@ -23,6 +24,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
23
24
  let context: MarkdownParser.Context = {
24
25
  ...settings,
25
26
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
+ backtrackable,
26
28
  };
27
29
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
28
30
  assert(!settings.id);
@@ -141,8 +143,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
141
143
 
142
144
  function nearest(index: number): HTMLElement | undefined {
143
145
  let el: HTMLElement | undefined;
144
- let len = 0;
145
- for (let i = 0; i < blocks.length; ++i) {
146
+ for (let i = 0, len = 0; i < blocks.length; ++i) {
146
147
  const block = blocks[i];
147
148
  len += block[0].length;
148
149
  el = block[1][0] ?? el;
@@ -152,8 +153,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
152
153
  }
153
154
 
154
155
  function index(source: HTMLElement): number {
155
- let len = 0;
156
- for (let i = 0; i < blocks.length; ++i) {
156
+ for (let i = 0, len = 0; i < blocks.length; ++i) {
157
157
  const block = blocks[i];
158
158
  if (block[1].includes(source)) return len;
159
159
  len += block[0].length;
@@ -2,9 +2,10 @@ import { location } from 'spica/global';
2
2
  import { ParserOptions } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
5
+ import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
5
6
  import { header } from '../header';
6
7
  import { block } from '../block';
7
- import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
8
+ import { backtrackable } from '../context';
8
9
  import { normalize } from './normalize';
9
10
  import { headers } from './header';
10
11
  import { figure } from '../processor/figure';
@@ -29,6 +30,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
29
30
  ...context?.resources && {
30
31
  resources: context.resources,
31
32
  },
33
+ backtrackable,
32
34
  };
33
35
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
34
36
  const node = frag();
@@ -1,5 +1,5 @@
1
1
  import { BlockquoteParser } from '../block';
2
- import { union, some, block, validate, rewrite, creator, open, convert, lazy, fmap } from '../../combinator';
2
+ import { union, some, creator, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
3
3
  import { autolink } from '../autolink';
4
4
  import { contentline } from '../source';
5
5
  import { parse } from '../api/parse';
@@ -1,23 +1,17 @@
1
1
  import { DListParser } from '../block';
2
- import { union, inits, some, block, line, validate, rewrite, context, creator, open, trimEnd, lazy, fmap } from '../../combinator';
2
+ import { union, inits, some, creator, state, block, line, validate, rewrite, open, trimEnd, lazy, fmap } from '../../combinator';
3
3
  import { inline, indexee, indexer } from '../inline';
4
4
  import { anyline } from '../source';
5
+ import { State } from '../context';
5
6
  import { localize } from '../locale';
6
- import { visualize, trimBlank } from '../util';
7
+ import { visualize, trimBlank } from '../visibility';
7
8
  import { html, defrag } from 'typed-dom/dom';
8
9
  import { push } from 'spica/array';
9
10
 
10
11
  export const dlist: DListParser = lazy(() => block(localize(fmap(validate(
11
12
  /^~[^\S\n]+(?=\S)/,
12
13
  some(inits([
13
- context({ syntax: { inline: {
14
- annotation: false,
15
- reference: false,
16
- index: false,
17
- label: false,
18
- link: false,
19
- media: false,
20
- }}},
14
+ state(State.annotation | State.reference | State.index | State.label | State.link | State.media,
21
15
  some(term)),
22
16
  some(desc),
23
17
  ]))),
@@ -1,6 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { ExtensionParser } from '../../block';
3
- import { union, inits, sequence, some, block, line, fence, rewrite, context, close, match, convert, trimEnd, fallback, fmap } from '../../../combinator';
3
+ import { union, inits, sequence, some, state, block, line, fence, rewrite, close, match, convert, trimEnd, fallback, fmap } from '../../../combinator';
4
4
  import { str, contentline, emptyline } from '../../source';
5
5
  import { label, segment as seg_label } from '../../inline/extension/label';
6
6
  import { ulist } from '../ulist';
@@ -13,8 +13,9 @@ import { table, segment_ as seg_table } from './table';
13
13
  import { blockquote, segment as seg_blockquote } from '../blockquote';
14
14
  import { placeholder, segment_ as seg_placeholder } from './placeholder';
15
15
  import { inline, media, shortmedia } from '../../inline';
16
+ import { State } from '../../context';
16
17
  import { localize } from '../../locale';
17
- import { visualize, trimBlank } from '../../util';
18
+ import { visualize, trimBlank } from '../../visibility';
18
19
  import { html, defrag } from 'typed-dom/dom';
19
20
  import { memoize } from 'spica/memoize';
20
21
 
@@ -66,7 +67,7 @@ export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
66
67
  ])),
67
68
  emptyline,
68
69
  block(localize(
69
- context({ syntax: { inline: { media: false } } },
70
+ state(State.media,
70
71
  visualize(trimBlank(trimEnd(some(inline))))))),
71
72
  ]),
72
73
  ])),
@@ -2,11 +2,11 @@ import { undefined, BigInt, Array } from 'spica/global';
2
2
  import { max, min, isArray } from 'spica/alias';
3
3
  import { ExtensionParser } from '../../block';
4
4
  import { Tree, eval } from '../../../combinator/data/parser';
5
- import { union, subsequence, inits, some, block, line, validate, fence, rewrite, creator, open, clear, convert, trim, dup, lazy, fmap } from '../../../combinator';
5
+ import { union, subsequence, inits, some, creator, block, line, validate, fence, rewrite, open, clear, convert, trim, dup, lazy, fmap } from '../../../combinator';
6
6
  import { inline } from '../../inline';
7
7
  import { str, anyline, emptyline, contentline } from '../../source';
8
8
  import { localize } from '../../locale';
9
- import { visualize } from '../../util';
9
+ import { visualize } from '../../visibility';
10
10
  import { html, define, defrag } from 'typed-dom/dom';
11
11
  import { unshift, splice } from 'spica/array';
12
12
 
@@ -1,8 +1,9 @@
1
1
  import { HeadingParser } from '../block';
2
- import { union, some, block, line, validate, focus, rewrite, context, open, fmap } from '../../combinator';
2
+ import { union, some, state, block, line, validate, focus, rewrite, open, fmap } from '../../combinator';
3
3
  import { inline, indexee, indexer } from '../inline';
4
4
  import { str } from '../source';
5
- import { visualize, trimBlank } from '../util';
5
+ import { State } from '../context';
6
+ import { visualize, trimBlank } from '../visibility';
6
7
  import { html, defrag } from 'typed-dom/dom';
7
8
 
8
9
  export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
@@ -10,23 +11,14 @@ export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
10
11
  some(line(source => [[source], ''])))));
11
12
 
12
13
  export const heading: HeadingParser = block(rewrite(segment,
13
- context({ syntax: { inline: {
14
- annotation: false,
15
- reference: false,
16
- index: false,
17
- label: false,
18
- link: false,
19
- media: false,
20
- }}},
14
+ state(State.annotation | State.reference | State.index | State.label | State.link | State.media,
21
15
  line(indexee(fmap(union([
22
16
  open(
23
17
  str(/^##+/),
24
18
  visualize(trimBlank(some(union([indexer, inline])))), true),
25
19
  open(
26
20
  str('#'),
27
- context({ syntax: { inline: {
28
- autolink: false,
29
- }}},
21
+ state(State.autolink,
30
22
  visualize(trimBlank(some(union([indexer, inline]))))), true),
31
23
  ]),
32
24
  ([h, ...ns]: [string, ...(HTMLElement | string)[]]) => [
@@ -1,13 +1,14 @@
1
1
  import { IListParser } from '../block';
2
- import { union, inits, some, block, line, validate, indent, context, creator, open, fallback, lazy, fmap } from '../../combinator';
2
+ import { union, inits, some, creator, state, block, line, validate, indent, open, fallback, lazy, fmap } from '../../combinator';
3
3
  import { ulist_, fillFirstLine } from './ulist';
4
4
  import { olist_, invalid } from './olist';
5
5
  import { inline } from '../inline';
6
+ import { State } from '../context';
6
7
  import { html, defrag } from 'typed-dom/dom';
7
8
 
8
9
  export const ilist: IListParser = lazy(() => block(validate(
9
10
  /^[-+*](?=[^\S\n]|\n[^\S\n]*\S)/,
10
- context({ syntax: { inline: { media: false } } },
11
+ state(State.media,
11
12
  ilist_))));
12
13
 
13
14
  export const ilist_: IListParser = lazy(() => block(fmap(validate(
@@ -1,13 +1,15 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { OListParser } from '../block';
3
- import { union, inits, subsequence, some, block, line, validate, indent, focus, rewrite, context, creator, open, match, fallback, lazy, fmap } from '../../combinator';
3
+ import { union, inits, subsequence, some, creator, state, block, line, validate, indent, focus, rewrite, open, match, fallback, lazy, fmap } from '../../combinator';
4
4
  import { checkbox, ulist_, fillFirstLine } from './ulist';
5
5
  import { ilist_ } from './ilist';
6
6
  import { inline, indexee, indexer } from '../inline';
7
7
  import { contentline } from '../source';
8
- import { trimBlank } from '../util';
8
+ import { State } from '../context';
9
+ import { trimBlank } from '../visibility';
9
10
  import { html, define, defrag } from 'typed-dom/dom';
10
11
  import { memoize } from 'spica/memoize';
12
+ import { duffbk } from 'spica/duff';
11
13
  import { shift } from 'spica/array';
12
14
  import { tuple } from 'spica/tuple';
13
15
 
@@ -21,7 +23,7 @@ export const olist: OListParser = lazy(() => block(validate(
21
23
  /^([0-9]+|[a-z]+|[A-Z]+)(?:-[0-9]+)*\.(?=[^\S\n]|\n[^\S\n]*\S)/.source,
22
24
  /^\(([0-9]+|[a-z]+)\)(?:-[0-9]+)*(?=[^\S\n]|\n[^\S\n]*\S)/.source,
23
25
  ].join('|')),
24
- context({ syntax: { inline: { media: false } } },
26
+ state(State.media,
25
27
  olist_))));
26
28
 
27
29
  export const olist_: OListParser = lazy(() => block(union([
@@ -122,15 +124,16 @@ function format(el: HTMLOListElement, type: string, form: string): HTMLOListElem
122
124
  'data-type': style(type) || undefined,
123
125
  });
124
126
  const marker = el.firstElementChild?.getAttribute('data-marker')!.match(initial(type))?.[0] ?? '';
125
- for (let es = el.children, len = es.length, i = 0; i < len; ++i) {
127
+ const es = el.children;
128
+ duffbk(es.length, i => {
126
129
  const el = es[i];
127
130
  switch (el.getAttribute('data-marker')) {
128
131
  case '':
129
132
  case marker:
130
133
  el.removeAttribute('data-marker');
131
- continue;
134
+ return;
132
135
  }
133
- break;
134
- }
136
+ return false;
137
+ });
135
138
  return el;
136
139
  }
@@ -2,7 +2,7 @@ import { ParagraphParser } from '../block';
2
2
  import { union, some, block, trimEnd, fmap } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { localize } from '../locale';
5
- import { visualize } from '../util';
5
+ import { visualize } from '../visibility';
6
6
  import { html, defrag } from 'typed-dom/dom';
7
7
 
8
8
  export const paragraph: ParagraphParser = block(localize(fmap(
@@ -1,5 +1,5 @@
1
1
  import { ReplyParser } from '../../block';
2
- import { union, tails, line, validate, focus, creator, reverse, fmap } from '../../../combinator';
2
+ import { union, tails, creator, line, validate, focus, reverse, fmap } from '../../../combinator';
3
3
  import { anchor } from '../../inline/autolink/anchor';
4
4
  import { str } from '../../source';
5
5
  import { html, define, defrag } from 'typed-dom/dom';
@@ -1,6 +1,6 @@
1
1
  import { ReplyParser } from '../../block';
2
2
  import { eval } from '../../../combinator/data/parser';
3
- import { union, some, block, line, validate, rewrite, creator, lazy, fmap } from '../../../combinator';
3
+ import { union, some, creator, block, line, validate, rewrite, lazy, fmap } from '../../../combinator';
4
4
  import { math } from '../../inline/math';
5
5
  import { str, anyline } from '../../source';
6
6
  import { autolink } from '../../autolink';
@@ -5,7 +5,7 @@ import { quote, syntax as delimiter } from './reply/quote';
5
5
  import { inline } from '../inline';
6
6
  import { anyline } from '../source';
7
7
  import { localize } from '../locale';
8
- import { visualize } from '../util';
8
+ import { visualize } from '../visibility';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
  import { push, pop } from 'spica/array';
11
11
 
@@ -1,5 +1,5 @@
1
1
  import { SidefenceParser } from '../block';
2
- import { union, some, block, focus, rewrite, creator, convert, lazy, fmap } from '../../combinator';
2
+ import { union, some, creator, block, focus, rewrite, convert, lazy, fmap } from '../../combinator';
3
3
  import { autolink } from '../autolink';
4
4
  import { contentline } from '../source';
5
5
  import { html, define, defrag } from 'typed-dom/dom';
@@ -29,6 +29,11 @@ describe('Unit: parser/block/table', () => {
29
29
  assert.deepStrictEqual(inspect(parser('|\n|-\n|')), [['<table><thead><tr></tr></thead><tbody><tr></tr></tbody></table>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser('||\n|-|\n||')), [['<table><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr></tbody></table>'], '']);
31
31
  assert.deepStrictEqual(inspect(parser('|||\n|-|-|\n|||')), [['<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr></tbody></table>'], '']);
32
+ assert.deepStrictEqual(inspect(parser('|"|\n|-\n|')), [['<table><thead><tr><th>"</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
33
+ assert.deepStrictEqual(inspect(parser('|`|\n|-\n|')), [['<table><thead><tr><th>`</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
34
+ assert.deepStrictEqual(inspect(parser('|`|`|\n|-\n|')), [['<table><thead><tr><th><code data-src="`|`">|</code></th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
35
+ assert.deepStrictEqual(inspect(parser('|((|\n|-\n|')), [['<table><thead><tr><th>((</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
36
+ assert.deepStrictEqual(inspect(parser('|${|\n|-\n|')), [['<table><thead><tr><th>${</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
32
37
  assert.deepStrictEqual(inspect(parser('|a|b|\n|-|-|\n|1|2|')), [['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>'], '']);
33
38
  assert.deepStrictEqual(inspect(parser('|a|b\n|-|-\n|1|2')), [['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>'], '']);
34
39
  assert.deepStrictEqual(inspect(parser('|a|\n|-|\n|1|')), [['<table><thead><tr><th>a</th></tr></thead><tbody><tr><td>1</td></tr></tbody></table>'], '']);
@@ -1,8 +1,10 @@
1
1
  import { TableParser } from '../block';
2
- import { union, sequence, some, block, line, validate, focus, rewrite, creator, surround, open, fallback, lazy, fmap } from '../../combinator';
2
+ import { union, sequence, some, creator, block, line, validate, focus, rewrite, surround, open, fallback, lazy, fmap } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { contentline } from '../source';
5
+ import { trimNode } from '../visibility';
5
6
  import { html, defrag } from 'typed-dom/dom';
7
+ import { duffEach, duffReduce } from 'spica/duff';
6
8
  import { push } from 'spica/array';
7
9
 
8
10
  import RowParser = TableParser.RowParser;
@@ -24,7 +26,7 @@ export const table: TableParser = lazy(() => block(fmap(validate(
24
26
  ])));
25
27
 
26
28
  const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean): RowParser<P> => creator(fallback(fmap(
27
- line(surround(/^(?=\|)/, some(union([parser])), /^\|?\s*$/, optional)),
29
+ line(surround(/^(?=\|)/, some(union([parser])), /^[|\\]?\s*$/, optional)),
28
30
  es => [html('tr', es)]),
29
31
  rewrite(contentline, source => [[
30
32
  html('tr', {
@@ -46,31 +48,30 @@ const align: AlignParser = creator(fmap(open(
46
48
  ns => [html('td', defrag(ns))]));
47
49
 
48
50
  const cell: CellParser = surround(
49
- /^\|(?:\\?\s)*(?=\S)/,
50
- some(union([inline]), /^(?:\\?\s)*(?=\||\\?$)/),
51
+ /^\|\s*(?=\S)/,
52
+ some(union([inline]), /^\|/, [[/^[|\\]?\s*$/, 9]]),
51
53
  /^[^|]*/, true);
52
54
 
53
55
  const head: CellParser.HeadParser = creator(fmap(
54
56
  cell,
55
- ns => [html('th', defrag(ns))]));
57
+ ns => [html('th', trimNode(defrag(ns)))]));
56
58
 
57
59
  const data: CellParser.DataParser = creator(fmap(
58
60
  cell,
59
- ns => [html('td', defrag(ns))]));
61
+ ns => [html('td', trimNode(defrag(ns)))]));
60
62
 
61
63
  function format(rows: HTMLTableRowElement[]): HTMLTableRowElement[] {
62
64
  const aligns = rows[0].classList.contains('invalid')
63
65
  ? []
64
- : push([], rows.shift()!.children).map(el => el.textContent!);
65
- for (let i = 0, len = rows.length; i < len; ++i) {
66
- const cols = rows[i].children;
67
- for (let i = 0, len = cols.length; i < len; ++i) {
66
+ : duffReduce(rows.shift()!.children, (acc, el) => push(acc, [el.textContent!]), [] as string[]);
67
+ for (let i = 0; i < rows.length; ++i) {
68
+ duffEach(rows[i].children, (col, i) => {
68
69
  if (i > 0 && !aligns[i]) {
69
70
  aligns[i] = aligns[i - 1];
70
71
  }
71
- if (!aligns[i]) continue;
72
- cols[i].setAttribute('align', aligns[i]);
73
- }
72
+ if (!aligns[i]) return;
73
+ col.setAttribute('align', aligns[i]);
74
+ });
74
75
  }
75
76
  return rows;
76
77
  }
@@ -1,15 +1,16 @@
1
1
  import { UListParser } from '../block';
2
- import { union, inits, subsequence, some, block, line, validate, indent, focus, context, creator, open, fallback, lazy, fmap } from '../../combinator';
2
+ import { union, inits, subsequence, some, creator, state, block, line, validate, indent, focus, open, fallback, lazy, fmap } from '../../combinator';
3
3
  import { olist_, invalid } from './olist';
4
4
  import { ilist_ } from './ilist';
5
5
  import { inline, indexer, indexee } from '../inline';
6
- import { trimBlank } from '../util';
6
+ import { State } from '../context';
7
+ import { trimBlank } from '../visibility';
7
8
  import { html, defrag } from 'typed-dom/dom';
8
9
  import { unshift } from 'spica/array';
9
10
 
10
11
  export const ulist: UListParser = lazy(() => block(validate(
11
12
  /^-(?=[^\S\n]|\n[^\S\n]*\S)/,
12
- context({ syntax: { inline: { media: false } } },
13
+ state(State.media,
13
14
  ulist_))));
14
15
 
15
16
  export const ulist_: UListParser = lazy(() => block(fmap(validate(
@@ -36,7 +36,7 @@ export import ReplyParser = BlockParser.ReplyParser;
36
36
  export import ParagraphParser = BlockParser.ParagraphParser;
37
37
 
38
38
  export const block: BlockParser = creator(error(
39
- reset({ resources: { budget: 50 * 1000, recursion: 20 + 1 } },
39
+ reset({ resources: { budget: 50 * 1000, recursion: 20 } },
40
40
  union([
41
41
  emptyline,
42
42
  horizontalrule,
@@ -0,0 +1,32 @@
1
+ export const enum Rule {
2
+ reference = 1 << 12,
3
+ comment = 1 << 11,
4
+ index = 1 << 10,
5
+ placeholder = 1 << 9,
6
+ link = 1 << 8,
7
+ bracket = 1 << 7,
8
+ media = 1 << 6,
9
+ annotation = 1 << 5,
10
+ mathbracket = 1 << 4,
11
+ html = 1 << 3,
12
+ math = 1 << 2,
13
+ autolink = 1 << 1,
14
+ quote = 1 << 0,
15
+ none = 0,
16
+ }
17
+ export const backtrackable = 0
18
+ | Rule.annotation
19
+ | Rule.reference
20
+ | Rule.index
21
+ | Rule.link
22
+ | Rule.media;
23
+
24
+ export const enum State {
25
+ annotation = 1 << 6,
26
+ reference = 1 << 5,
27
+ index = 1 << 4,
28
+ label = 1 << 3,
29
+ link = 1 << 2,
30
+ media = 1 << 1,
31
+ autolink = 1 << 0,
32
+ }
@@ -1,5 +1,5 @@
1
1
  import { MarkdownParser } from '../../markdown';
2
- import { union, inits, some, block, line, validate, focus, rewrite, guard, clear, convert, lazy, fmap } from '../combinator';
2
+ import { union, inits, some, guard, block, line, validate, focus, rewrite, clear, convert, lazy, fmap } from '../combinator';
3
3
  import { segment } from './segment';
4
4
  import { str } from './source';
5
5
  import { normalize } from './api/normalize';
@@ -1,28 +1,20 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { AnnotationParser } from '../inline';
3
- import { union, some, validate, guard, context, precedence, creator, recursion, surround, lazy } from '../../combinator';
3
+ import { union, some, guard, context, syntax, state, validate, surround, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { optimize } from './link';
6
- import { startLoose, trimNode } from '../util';
6
+ import { Rule, State } from '../context';
7
+ import { startLoose, trimNode } from '../visibility';
7
8
  import { html, defrag } from 'typed-dom/dom';
8
9
 
9
- export const annotation: AnnotationParser = lazy(() => creator(recursion(precedence(6, validate('((', surround(
10
+ export const annotation: AnnotationParser = lazy(() => validate('((', syntax(Rule.annotation, 6, surround(
10
11
  '((',
11
- guard(context => context.syntax?.inline?.annotation ?? true,
12
+ guard(context => ~context.state! & State.annotation,
13
+ state(State.annotation | State.media,
12
14
  startLoose(
13
- context({ syntax: { inline: {
14
- annotation: false,
15
- // Redundant
16
- //reference: true,
17
- media: false,
18
- // Redundant
19
- //index: true,
20
- //label: true,
21
- //link: true,
22
- //autolink: true,
23
- }}, delimiters: undefined },
24
- some(union([inline]), ')', [[/^\\?\n/, 9], [')', 3], ['))', 6]])), ')')),
15
+ context({ delimiters: undefined },
16
+ some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')'))),
25
17
  '))',
26
18
  false,
27
19
  ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest],
28
- ([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest)))))));
20
+ ([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest)))));
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { verify, rewrite, creator } from '../../../combinator';
2
+ import { creator, verify, rewrite } from '../../../combinator';
3
3
  import { str } from '../../source';
4
4
  import { html } from 'typed-dom/dom';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../../inline';
2
- import { union, some, validate, focus, rewrite, precedence, creator, convert, surround, open, lazy } from '../../../combinator';
2
+ import { union, some, creator, precedence, validate, focus, rewrite, convert, surround, open, lazy } from '../../../combinator';
3
3
  import { textlink } from '../link';
4
4
  import { unescsource } from '../../source';
5
5
 
@@ -13,7 +13,7 @@ export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'ht
13
13
  url => `{ ${url} }`,
14
14
  union([textlink])))));
15
15
 
16
- const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creator(precedence(3, union([
16
+ const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => creator(precedence(2, union([
17
17
  surround('(', some(union([bracket, unescsource]), ')'), ')', true),
18
18
  surround('[', some(union([bracket, unescsource]), ']'), ']', true),
19
19
  surround('{', some(union([bracket, unescsource]), '}'), '}', true),
@@ -1,5 +1,5 @@
1
1
  import { AutolinkParser } from '../inline';
2
- import { union, some, validate, guard, fmap } from '../../combinator';
2
+ import { union, some, syntax, guard, validate, fmap } from '../../combinator';
3
3
  import { url } from './autolink/url';
4
4
  import { email } from './autolink/email';
5
5
  import { channel } from './autolink/channel';
@@ -8,11 +8,13 @@ import { hashtag, emoji } from './autolink/hashtag';
8
8
  import { hashnum } from './autolink/hashnum';
9
9
  import { anchor } from './autolink/anchor';
10
10
  import { str } from '../source';
11
+ import { Rule, State } from '../context';
11
12
  import { stringify } from '../util';
12
13
 
13
14
  export const autolink: AutolinkParser = fmap(
14
15
  validate(/^(?:[@#>0-9A-Za-z]|\S#)/,
15
- guard(context => context.syntax?.inline?.autolink ?? true,
16
+ guard(context => ~context.state! & State.autolink,
17
+ syntax(Rule.autolink, 1,
16
18
  some(union([
17
19
  url,
18
20
  email,
@@ -29,5 +31,5 @@ export const autolink: AutolinkParser = fmap(
29
31
  // Escape unmatched hashtag-like strings.
30
32
  str(new RegExp(/^#+(?:[^\p{C}\p{S}\p{P}\s]|emoji|['_])*/u.source.replace('emoji', emoji), 'u')),
31
33
  anchor,
32
- ])))),
34
+ ]))))),
33
35
  ns => ns.length === 1 ? ns : [stringify(ns)]);
@@ -39,6 +39,7 @@ describe('Unit: parser/inline/bracket', () => {
39
39
  assert.deepStrictEqual(inspect(parser('(ABBR, ABBR)')), [['(', 'ABBR, ABBR', ')'], '']);
40
40
  assert.deepStrictEqual(inspect(parser('(\\a)')), [['<span class="paren">(a)</span>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('(==)')), [['<span class="paren">(==)</span>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('($)$')), [['', '(', '<span class="math" translate="no" data-src="$)$">$)$</span>'], '']);
42
43
  assert.deepStrictEqual(inspect(parser(')')), undefined);
43
44
  assert.deepStrictEqual(inspect(parser('(1,2)')), [['(', '1,2', ')'], '']);
44
45
  assert.deepStrictEqual(inspect(parser('(0-1)')), [['(', '0-1', ')'], '']);
@@ -55,6 +56,7 @@ describe('Unit: parser/inline/bracket', () => {
55
56
  assert.deepStrictEqual(inspect(parser('[a')), [['', '[', 'a'], '']);
56
57
  assert.deepStrictEqual(inspect(parser('[a]')), [['[', 'a', ']'], '']);
57
58
  assert.deepStrictEqual(inspect(parser('[==]')), [['[', '==', ']'], '']);
59
+ assert.deepStrictEqual(inspect(parser('[$]$')), [['', '[', '<span class="math" translate="no" data-src="$]$">$]$</span>'], '']);
58
60
  assert.deepStrictEqual(inspect(parser(']')), undefined);
59
61
  });
60
62
 
@@ -1,30 +1,31 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { BracketParser } from '../inline';
3
- import { union, some, precedence, creator, surround, lazy } from '../../combinator';
3
+ import { union, some, syntax, surround, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
+ import { Rule } from '../context';
6
7
  import { html, defrag } from 'typed-dom/dom';
7
8
  import { unshift, push } from 'spica/array';
8
9
 
9
10
  const index = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*/;
10
11
 
11
- export const bracket: BracketParser = lazy(() => creator(0, union([
12
- surround(str('('), precedence(3, str(index)), str(')')),
13
- surround(str('('), precedence(3, some(inline, ')', [[')', 3]])), str(')'), true,
12
+ export const bracket: BracketParser = lazy(() => union([
13
+ syntax(Rule.none, 2, surround(str('('), str(index), str(')'))),
14
+ syntax(Rule.bracket, 2, surround(str('('), some(inline, ')', [[')', 2]]), str(')'), true,
14
15
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
15
- ([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest]),
16
- surround(str('('), precedence(3, str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0))))), str(')')),
17
- surround(str('('), precedence(3, some(inline, ')', [[')', 3]])), str(')'), true,
16
+ ([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest])),
17
+ syntax(Rule.none, 2, surround(str('('), str(new RegExp(index.source.replace(', ', '[,、]').replace(/[09AZaz.]|\-(?!\w)/g, c => c.trimStart() && String.fromCharCode(c.charCodeAt(0) + 0xFEE0)))), str(')'))),
18
+ syntax(Rule.bracket, 2, surround(str('('), some(inline, ')', [[')', 2]]), str(')'), true,
18
19
  ([as, bs = [], cs], rest) => [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))], rest],
19
- ([as, bs = []], rest) => [unshift(as, bs), rest]),
20
- surround(str('['), precedence(3, some(inline, ']', [[']', 3]])), str(']'), true,
20
+ ([as, bs = []], rest) => [unshift(as, bs), rest])),
21
+ syntax(Rule.bracket, 2, surround(str('['), some(inline, ']', [[']', 2]]), str(']'), true,
21
22
  undefined,
22
- ([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest]),
23
- surround(str('{'), precedence(3, some(inline, '}', [['}', 3]])), str('}'), true,
23
+ ([as, bs = []], rest) => [unshift([''], unshift(as, bs)), rest])),
24
+ syntax(Rule.bracket, 2, surround(str('{'), some(inline, '}', [['}', 2]]), str('}'), true,
24
25
  undefined,
25
- ([as, bs = []], rest) => [unshift(as, bs), rest]),
26
+ ([as, bs = []], rest) => [unshift(as, bs), rest])),
26
27
  // Control media blinking in editing rather than control confusion of pairs of quote marks.
27
- surround(str('"'), precedence(8, some(inline, '"', [['"', 8]])), str('"'), true,
28
+ syntax(Rule.quote, 8, surround(str('"'), some(inline, '"', [['"', 8]]), str('"'), true,
28
29
  undefined,
29
- ([as, bs = []], rest) => [unshift(as, bs), rest]),
30
- ])));
30
+ ([as, bs = []], rest) => [unshift(as, bs), rest])),
31
+ ]));
@@ -1,5 +1,5 @@
1
1
  import { CodeParser } from '../inline';
2
- import { validate, creator, match } from '../../combinator';
2
+ import { creator, validate, match } from '../../combinator';
3
3
  import { html } from 'typed-dom/dom';
4
4
 
5
5
  export const code: CodeParser = creator(validate('`', match(