securemark 0.293.5 → 0.294.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 (105) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.js +845 -534
  3. package/markdown.d.ts +13 -13
  4. package/package.json +3 -3
  5. package/src/combinator/control/constraint/block.test.ts +6 -6
  6. package/src/combinator/control/constraint/contract.ts +3 -3
  7. package/src/combinator/control/constraint/line.test.ts +7 -7
  8. package/src/combinator/control/constraint/line.ts +1 -1
  9. package/src/combinator/control/manipulation/clear.ts +2 -3
  10. package/src/combinator/control/manipulation/convert.ts +2 -2
  11. package/src/combinator/control/manipulation/duplicate.ts +4 -5
  12. package/src/combinator/control/manipulation/fence.ts +2 -2
  13. package/src/combinator/control/manipulation/indent.test.ts +2 -2
  14. package/src/combinator/control/manipulation/indent.ts +4 -4
  15. package/src/combinator/control/manipulation/reverse.ts +2 -2
  16. package/src/combinator/control/manipulation/scope.ts +3 -4
  17. package/src/combinator/control/manipulation/surround.ts +12 -13
  18. package/src/combinator/control/monad/bind.ts +6 -6
  19. package/src/combinator/control/monad/fmap.ts +7 -7
  20. package/src/combinator/data/data.ts +135 -0
  21. package/src/combinator/data/parser/context.test.ts +8 -8
  22. package/src/combinator/data/parser/context.ts +3 -3
  23. package/src/combinator/data/parser/inits.ts +6 -7
  24. package/src/combinator/data/parser/sequence.test.ts +3 -3
  25. package/src/combinator/data/parser/sequence.ts +6 -7
  26. package/src/combinator/data/parser/some.test.ts +3 -3
  27. package/src/combinator/data/parser/some.ts +4 -4
  28. package/src/combinator/data/parser/subsequence.test.ts +4 -4
  29. package/src/combinator/data/parser/subsequence.ts +3 -3
  30. package/src/combinator/data/parser/tails.ts +3 -3
  31. package/src/combinator/data/parser/union.test.ts +3 -3
  32. package/src/combinator/data/parser.ts +16 -7
  33. package/src/debug.test.ts +6 -5
  34. package/src/parser/api/bind.ts +4 -6
  35. package/src/parser/api/header.ts +1 -1
  36. package/src/parser/api/normalize.ts +1 -1
  37. package/src/parser/api/parse.ts +3 -1
  38. package/src/parser/block/blockquote.ts +6 -4
  39. package/src/parser/block/codeblock.ts +8 -7
  40. package/src/parser/block/dlist.ts +9 -8
  41. package/src/parser/block/extension/aside.ts +27 -21
  42. package/src/parser/block/extension/example.ts +29 -26
  43. package/src/parser/block/extension/fig.ts +1 -1
  44. package/src/parser/block/extension/figbase.ts +6 -5
  45. package/src/parser/block/extension/figure.ts +23 -19
  46. package/src/parser/block/extension/message.ts +35 -24
  47. package/src/parser/block/extension/placeholder.ts +17 -13
  48. package/src/parser/block/extension/table.ts +47 -40
  49. package/src/parser/block/heading.test.ts +3 -12
  50. package/src/parser/block/heading.ts +12 -8
  51. package/src/parser/block/ilist.ts +13 -12
  52. package/src/parser/block/mathblock.ts +21 -17
  53. package/src/parser/block/mediablock.ts +7 -5
  54. package/src/parser/block/olist.ts +15 -5
  55. package/src/parser/block/pagebreak.ts +2 -1
  56. package/src/parser/block/paragraph.ts +3 -1
  57. package/src/parser/block/reply/cite.ts +20 -15
  58. package/src/parser/block/reply/quote.ts +6 -4
  59. package/src/parser/block/reply.ts +6 -3
  60. package/src/parser/block/sidefence.ts +8 -7
  61. package/src/parser/block/table.ts +23 -22
  62. package/src/parser/block/ulist.ts +16 -12
  63. package/src/parser/block.ts +7 -6
  64. package/src/parser/header.ts +18 -18
  65. package/src/parser/inline/annotation.ts +3 -1
  66. package/src/parser/inline/autolink/account.ts +3 -2
  67. package/src/parser/inline/autolink/anchor.ts +3 -2
  68. package/src/parser/inline/autolink/channel.ts +5 -4
  69. package/src/parser/inline/autolink/email.ts +4 -3
  70. package/src/parser/inline/autolink/hashnum.ts +3 -2
  71. package/src/parser/inline/autolink/hashtag.ts +4 -3
  72. package/src/parser/inline/autolink/url.ts +7 -6
  73. package/src/parser/inline/bracket.ts +16 -15
  74. package/src/parser/inline/code.ts +5 -4
  75. package/src/parser/inline/deletion.ts +5 -5
  76. package/src/parser/inline/emphasis.ts +4 -3
  77. package/src/parser/inline/emstrong.test.ts +18 -18
  78. package/src/parser/inline/emstrong.ts +39 -30
  79. package/src/parser/inline/extension/index.ts +22 -19
  80. package/src/parser/inline/extension/indexee.ts +2 -2
  81. package/src/parser/inline/extension/indexer.ts +2 -1
  82. package/src/parser/inline/extension/label.ts +7 -3
  83. package/src/parser/inline/extension/placeholder.ts +6 -6
  84. package/src/parser/inline/html.ts +27 -28
  85. package/src/parser/inline/htmlentity.ts +9 -8
  86. package/src/parser/inline/insertion.ts +5 -5
  87. package/src/parser/inline/italic.ts +5 -5
  88. package/src/parser/inline/link.ts +36 -38
  89. package/src/parser/inline/mark.ts +7 -7
  90. package/src/parser/inline/math.ts +5 -4
  91. package/src/parser/inline/media.ts +33 -32
  92. package/src/parser/inline/reference.ts +19 -20
  93. package/src/parser/inline/remark.ts +11 -11
  94. package/src/parser/inline/ruby.ts +50 -53
  95. package/src/parser/inline/strong.ts +4 -3
  96. package/src/parser/inline/template.ts +16 -15
  97. package/src/parser/inline.test.ts +3 -3
  98. package/src/parser/segment.ts +3 -1
  99. package/src/parser/source/escapable.ts +9 -8
  100. package/src/parser/source/line.ts +4 -3
  101. package/src/parser/source/str.ts +2 -2
  102. package/src/parser/source/text.ts +9 -8
  103. package/src/parser/source/unescapable.ts +6 -5
  104. package/src/parser/util.ts +18 -13
  105. package/src/parser/visibility.ts +19 -20
@@ -1,14 +1,13 @@
1
1
  import { MediaParser } from '../inline';
2
2
  import { State, Recursion, Backtrack, Command } from '../context';
3
- import { subinput } from '../../combinator/data/parser';
4
- import { union, inits, tails, some, creation, recursion, precedence, constraint, verify, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
3
+ import { List, Data, subinput } from '../../combinator/data/parser';
4
+ import { union, inits, tails, some, creation, recursion, precedence, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
5
5
  import { unsafelink, uri, option as linkoption, resolve, decode } from './link';
6
6
  import { attributes } from './html';
7
7
  import { unsafehtmlentity } from './htmlentity';
8
8
  import { txt, str } from '../source';
9
- import { invalid } from '../util';
9
+ import { unwrap, invalid } from '../util';
10
10
  import { ReadonlyURL } from 'spica/url';
11
- import { unshift, push } from 'spica/array';
12
11
  import { html, define } from 'typed-dom/dom';
13
12
 
14
13
  const optspec = {
@@ -21,7 +20,7 @@ Object.setPrototypeOf(optspec, null);
21
20
 
22
21
  export const media: MediaParser = lazy(() => constraint(State.media, creation(10, open(
23
22
  '!',
24
- bind(verify(fmap(tails([
23
+ bind(fmap(tails([
25
24
  dup(surround(
26
25
  '[',
27
26
  precedence(1, some(union([
@@ -31,9 +30,9 @@ export const media: MediaParser = lazy(() => constraint(State.media, creation(10
31
30
  ]), ']')),
32
31
  ']',
33
32
  true,
34
- ([, ns = []], context) =>
33
+ ([, ns = new List()], context) =>
35
34
  context.linebreak === 0
36
- ? [ns]
35
+ ? ns
37
36
  : undefined,
38
37
  undefined,
39
38
  [3 | Backtrack.escbracket])),
@@ -47,26 +46,29 @@ export const media: MediaParser = lazy(() => constraint(State.media, creation(10
47
46
  if (!bs) return;
48
47
  const head = context.position - context.range!;
49
48
  setBacktrack(context, [2 | Backtrack.link], head);
50
- return [push(unshift(as, bs), [Command.Cancel])];
49
+ return as.import(bs).push(new Data(Command.Cancel)) && as;
51
50
  },
52
51
  [3 | Backtrack.link])),
53
52
  ]),
54
- ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]),
55
- ([[text]]) => text === '' || text.trim() !== ''),
56
- ([[text], params], context) => {
57
- if (params.at(-1) === Command.Cancel) {
53
+ nodes =>
54
+ nodes.length === 1
55
+ ? new List<Data<List<Data<string>>>>([new Data(new List([new Data('')])), nodes.delete(nodes.head!)])
56
+ : new List<Data<List<Data<string>>>>([new Data(new List([new Data(nodes.head!.value.foldl((acc, { value }) => acc + value, ''))])), nodes.delete(nodes.last!)])),
57
+ ([{ value: [{ value: text }] }, { value: params }], context) => {
58
+ if (text && text.trimStart() === '') return;
59
+ text = text.trim();
60
+ if (params.last!.value === Command.Cancel) {
58
61
  params.pop();
59
- return [[
60
- html('span',
62
+ return new List([
63
+ new Data(html('span',
61
64
  {
62
65
  class: 'invalid',
63
66
  ...invalid('media', 'syntax', 'Missing the closing symbol "}"')
64
67
  },
65
- '!' + context.source.slice(context.position - context.range!, context.position))
66
- ]];
68
+ '!' + context.source.slice(context.position - context.range!, context.position)))
69
+ ]);
67
70
  }
68
- assert(text === text.trim());
69
- const INSECURE_URI = params.shift()!;
71
+ const INSECURE_URI = params.shift()!.value;
70
72
  assert(INSECURE_URI === INSECURE_URI.trim());
71
73
  assert(!INSECURE_URI.match(/\s/));
72
74
  // altが空だとエラーが見えないため埋める。
@@ -85,37 +87,37 @@ export const media: MediaParser = lazy(() => constraint(State.media, creation(10
85
87
  || html('img', { class: 'media', 'data-src': uri?.source });
86
88
  assert(!el.matches('.invalid'));
87
89
  el.setAttribute('alt', text);
88
- if (!sanitize(el, uri)) return [[el]];
90
+ if (!sanitize(el, uri)) return new List([new Data(el)]);
89
91
  assert(!el.matches('.invalid'));
90
- const [attrs, linkparams] = attributes('media', optspec, params);
92
+ const [attrs, linkparams] = attributes('media', optspec, unwrap(params));
91
93
  define(el, attrs);
92
94
  assert(el.matches('img') || !el.matches('.invalid'));
93
95
  // Awaiting the generic support for attr().
94
96
  if (el.hasAttribute('aspect-ratio')) {
95
97
  el.style.aspectRatio = el.getAttribute('aspect-ratio')!;
96
98
  }
97
- if (context.state! & State.link) return [[el]];
98
- if (cache && cache.tagName !== 'IMG') return [[el]];
99
+ if (context.state! & State.link) return new List([new Data(el)]);
100
+ if (cache && cache.tagName !== 'IMG') return new List([new Data(el)]);
99
101
  const { source, position } = context;
100
102
  return fmap(
101
103
  unsafelink as MediaParser,
102
- ([link]) => {
104
+ ([{ value }]) => {
103
105
  context.source = source;
104
106
  context.position = position;
105
- return [define(link, { class: null, target: '_blank' }, [el])];
107
+ return new List([new Data(define(value, { class: null, target: '_blank' }, [el]))]);
106
108
  })
107
109
  (subinput(`{ ${INSECURE_URI}${linkparams.join('')} }`, context));
108
110
  })))));
109
111
 
110
112
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => recursion(Recursion.terminal, union([
111
113
  surround(str('('), some(union([unsafehtmlentity, bracket, txt]), ')'), str(')'), true,
112
- undefined, () => [[]], [3 | Backtrack.escbracket]),
114
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
113
115
  surround(str('['), some(union([unsafehtmlentity, bracket, txt]), ']'), str(']'), true,
114
- undefined, () => [[]], [3 | Backtrack.escbracket]),
116
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
115
117
  surround(str('{'), some(union([unsafehtmlentity, bracket, txt]), '}'), str('}'), true,
116
- undefined, () => [[]], [3 | Backtrack.escbracket]),
118
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
117
119
  surround(str('"'), precedence(2, some(union([unsafehtmlentity, txt]), '"')), str('"'), true,
118
- undefined, () => [[]], [3 | Backtrack.escbracket]),
120
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
119
121
  ])));
120
122
 
121
123
  const option: MediaParser.ParameterParser.OptionParser = lazy(() => union([
@@ -124,11 +126,10 @@ const option: MediaParser.ParameterParser.OptionParser = lazy(() => union([
124
126
  str(/[x:]/y),
125
127
  str(/[1-9][0-9]*(?=[ }])/y),
126
128
  false,
127
- ([[a], [b], [c]]) => [
129
+ ([[{ value: a }], [{ value: b }], [{ value: c }]]) =>
128
130
  b === 'x'
129
- ? [`width="${a}"`, `height="${c}"`]
130
- : [`aspect-ratio="${a}/${c}"`],
131
- ]),
131
+ ? new List([new Data(`width="${a}"`), new Data(`height="${c}"`)])
132
+ : new List([new Data(`aspect-ratio="${a}/${c}"`)])),
132
133
  linkoption,
133
134
  ]));
134
135
 
@@ -1,14 +1,13 @@
1
1
  import { ReferenceParser } from '../inline';
2
2
  import { State, Backtrack, Command } from '../context';
3
- import { eval } from '../../combinator/data/parser';
3
+ import { List, Data, eval } from '../../combinator/data/parser';
4
4
  import { union, subsequence, some, precedence, state, constraint, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
6
  import { textlink } from './link';
7
7
  import { str } from '../source';
8
8
  import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
9
+ import { unwrap, invalid } from '../util';
9
10
  import { html, defrag } from 'typed-dom/dom';
10
- import { unshift, push } from 'spica/array';
11
- import { invalid } from '../util';
12
11
 
13
12
  export const reference: ReferenceParser = lazy(() => constraint(State.reference, surround(
14
13
  str('[['),
@@ -22,7 +21,7 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
22
21
  ([, ns], context) => {
23
22
  const { position, range = 0, linebreak = 0 } = context;
24
23
  if (linebreak === 0) {
25
- return [[html('sup', attributes(ns), [html('span', defrag(trimBlankNodeEnd(ns)))])]];
24
+ return new List([new Data(html('sup', attributes(ns), [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))]);
26
25
  }
27
26
  else {
28
27
  const head = position - range;
@@ -42,13 +41,13 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
42
41
  else {
43
42
  assert(source[position] === ']');
44
43
  if (state & State.annotation) {
45
- push(bs, [source[position]]);
44
+ bs.push(new Data(source[position]));
46
45
  }
47
46
  context.position += 1;
48
47
  let result: ReturnType<typeof textlink>;
49
48
  if (source[context.position] !== '{') {
50
49
  setBacktrack(context, [2 | Backtrack.link], head + 1);
51
- result = [[]];
50
+ result = new List();
52
51
  }
53
52
  else {
54
53
  result = !isBacktrack(context, [1 | Backtrack.link])
@@ -57,7 +56,7 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
57
56
  context.range = range;
58
57
  if (!result) {
59
58
  setBacktrack(context, [2 | Backtrack.link], head + 1);
60
- result = [[]];
59
+ result = new List();
61
60
  }
62
61
  }
63
62
  assert(result);
@@ -71,21 +70,21 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
71
70
  some(inline, ']', [[']', 1]]),
72
71
  str(']'),
73
72
  true,
74
- ([, cs = [], ds]) =>
75
- [push(cs, ds)],
76
- ([, cs = []]) => {
73
+ ([, cs = new List(), ds]) =>
74
+ cs.import(ds),
75
+ ([, cs = new List()]) => {
77
76
  setBacktrack(context, [2 | Backtrack.link], head);
78
- return [cs];
77
+ return cs;
79
78
  })
80
79
  ({ context });
81
80
  if (state & State.annotation && next) {
82
- return [push(push(unshift(as, bs), eval(result)), eval(next))];
81
+ return (as as List<Data<string | HTMLElement>>).import(bs).import(eval(result!)).import(eval(next));
83
82
  }
84
83
  }
85
84
  context.position = position;
86
85
  }
87
86
  return state & State.annotation
88
- ? [unshift(as, bs)]
87
+ ? as.import(bs as List<Data<string>>)
89
88
  : undefined;
90
89
  },
91
90
  [1 | Backtrack.bracket, 3 | Backtrack.doublebracket])));
@@ -98,22 +97,22 @@ const abbr: ReferenceParser.AbbrParser = surround(
98
97
  true,
99
98
  ([, ns], context) => {
100
99
  const { source, position, range = 0 } = context;
101
- if (!ns) return [['', source.slice(position - range, source[position - 1] === '|' ? position - 1 : position)]];
100
+ if (!ns) return new List([new Data(''), new Data(source.slice(position - range, source[position - 1] === '|' ? position - 1 : position))]);
102
101
  context.position += source[position] === ' ' ? 1 : 0;
103
- return [[Command.Separator, ns[0].trimEnd()]];
102
+ return new List([new Data(Command.Separator), new Data(ns.head!.value.trimEnd())]);
104
103
  },
105
104
  (_, context) => {
106
105
  context.position -= context.range!;
107
- return [['']];
106
+ return new List([new Data('')]);
108
107
  });
109
108
 
110
- function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
111
- switch (ns[0]) {
109
+ function attributes(ns: List<Data<string | HTMLElement>>): Record<string, string | undefined> {
110
+ switch (ns.head!.value) {
112
111
  case '':
113
112
  return { class: 'invalid', ...invalid('reference', 'syntax', 'Invalid abbreviation') };
114
113
  case Command.Separator:
115
- const abbr = ns[1] as string;
116
- ns[0] = ns[1] = '';
114
+ const abbr = ns.head!.next!.value as string;
115
+ ns.head!.value = ns.head!.next!.value = '';
117
116
  return { class: 'reference', 'data-abbr': abbr };
118
117
  default:
119
118
  return { class: 'reference' };
@@ -1,10 +1,10 @@
1
1
  import { RemarkParser } from '../inline';
2
2
  import { Recursion } from '../context';
3
+ import { List, Data } from '../../combinator/data/parser';
3
4
  import { union, some, recursion, precedence, focus, surround, close, fallback, lazy } from '../../combinator';
4
5
  import { inline } from '../inline';
5
6
  import { text, str } from '../source';
6
- import { invalid } from '../util';
7
- import { unshift, push } from 'spica/array';
7
+ import { unwrap, invalid } from '../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
10
10
  export const remark: RemarkParser = lazy(() => fallback(surround(
@@ -12,13 +12,13 @@ export const remark: RemarkParser = lazy(() => fallback(surround(
12
12
  precedence(3, recursion(Recursion.inline,
13
13
  some(union([inline]), /\s%\]/y, [[/\s%\]/y, 3]]))),
14
14
  close(text, str(`%]`)), true,
15
- ([as, bs = [], cs]) => [[
16
- html('span', { class: 'remark' }, [
15
+ ([as, bs = new List(), cs]) => new List([
16
+ new Data(html('span', { class: 'remark' }, [
17
17
  html('input', { type: 'checkbox' }),
18
- html('span', defrag(push(unshift(as, bs), cs))),
19
- ]),
20
- ]],
21
- ([as, bs]) => bs && [unshift(as, bs)]),
22
- focus(/\[%+(?=\s)/y, ({ context: { source } }) => [[
23
- html('span', { class: 'invalid', ...invalid('remark', 'syntax', 'Invalid start symbol') }, source)
24
- ]])));
18
+ html('span', defrag(unwrap(as.import(bs as List<Data<string>>).import(cs)))),
19
+ ])),
20
+ ]),
21
+ ([as, bs]) => bs && as.import(bs as List<Data<string>>)),
22
+ focus(/\[%+(?=\s)/y, ({ context: { source } }) => new List([
23
+ new Data(html('span', { class: 'invalid', ...invalid('remark', 'syntax', 'Invalid start symbol') }, source))
24
+ ]))));
@@ -1,11 +1,11 @@
1
1
  import { RubyParser } from '../inline';
2
2
  import { Backtrack } from '../context';
3
- import { eval } from '../../combinator/data/parser';
3
+ import { List, Data, eval } from '../../combinator/data/parser';
4
4
  import { inits, surround, setBacktrack, dup, lazy, bind } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
6
  import { txt } from '../source';
7
7
  import { isTightNodeStart } from '../visibility';
8
- import { unshift, push } from 'spica/array';
8
+ import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const ruby: RubyParser = lazy(() => bind(
@@ -14,8 +14,8 @@ export const ruby: RubyParser = lazy(() => bind(
14
14
  '[', text, ']',
15
15
  false,
16
16
  ([, ns]) => {
17
- ns && ns.at(-1) === '' && ns.pop();
18
- return isTightNodeStart(ns) ? [ns] : undefined;
17
+ ns && ns.last?.value === '' && ns.pop();
18
+ return isTightNodeStart(ns) ? ns : undefined;
19
19
  },
20
20
  undefined,
21
21
  [1 | Backtrack.bracket, 3 | Backtrack.ruby])),
@@ -24,48 +24,49 @@ export const ruby: RubyParser = lazy(() => bind(
24
24
  false, undefined, undefined,
25
25
  [1 | Backtrack.bracket, 3 | Backtrack.ruby])),
26
26
  ]),
27
- ([texts, rubies], context) => {
27
+ ([{ value: texts }, { value: rubies = undefined } = {}], context) => {
28
28
  if (rubies === undefined) {
29
29
  const head = context.position - context.range!;
30
30
  return void setBacktrack(context, [2 | Backtrack.ruby], head);
31
31
  }
32
32
  switch (true) {
33
- case rubies.length <= texts.length:
34
- return [[
35
- html('ruby', defrag(texts
36
- .reduce((acc, _, i) =>
37
- push(acc, unshift(
38
- [texts[i]],
39
- i < rubies.length && rubies[i]
40
- ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
41
- : [html('rt')]))
42
- , []))),
43
- ]];
44
- case texts.length === 1 && [...texts[0]].length >= rubies.length:
45
- return [[
46
- html('ruby', defrag([...texts[0]]
47
- .reduce((acc, _, i, texts) =>
48
- push(acc, unshift(
49
- [texts[i]],
50
- i < rubies.length && rubies[i]
51
- ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
52
- : [html('rt')]))
53
- , []))),
54
- ]];
33
+ case texts.length >= rubies.length:
34
+ return new List([
35
+ new Data(html('ruby', defrag(unwrap([...zip(texts, rubies)]
36
+ .reduce((acc, [{ value: text = '' } = {}, { value: ruby = '' } = {}]) =>
37
+ acc.import(
38
+ ruby
39
+ ? new List([new Data(text), new Data(html('rp', '(')), new Data(html('rt', ruby)), new Data(html('rp', ')'))])
40
+ : new List([new Data(text), new Data(html('rt'))]))
41
+ , new List<Data<string | HTMLElement>>()))))),
42
+ ]);
43
+ case texts.length === 1 && [...texts.head!.value].length >= rubies.length:
44
+ return new List([
45
+ new Data(html('ruby', defrag(unwrap([...zip(new List([...texts.head!.value].map(char => new Data(char))), rubies)]
46
+ .reduce((acc, [{ value: text = '' } = {}, { value: ruby = '' } = {}]) =>
47
+ acc.import(
48
+ ruby
49
+ ? new List([new Data(text), new Data(html('rp', '(')), new Data(html('rt', ruby)), new Data(html('rp', ')'))])
50
+ : new List([new Data(text), new Data(html('rt'))]))
51
+ , new List<Data<string | HTMLElement>>()))))),
52
+ ]);
55
53
  default:
56
54
  assert(rubies.length > 0);
57
- return [[
58
- html('ruby', defrag(unshift(
59
- [texts.join(' ')],
60
- [html('rp', '('), html('rt', rubies.join(' ').trim()), html('rp', ')')]))),
61
- ]];
55
+ return new List([
56
+ new Data(html('ruby', defrag(unwrap(new List<Data<string | HTMLElement>>([
57
+ new Data(texts.foldr(({ value }, acc) => value + ' ' + acc, '').slice(0, -1)),
58
+ new Data(html('rp', '(')),
59
+ new Data(html('rt', rubies.foldr(({ value }, acc) => value + ' ' + acc, '').trim())),
60
+ new Data(html('rp', ')')),
61
+ ]))))),
62
+ ]);
62
63
  }
63
64
  }));
64
65
 
65
66
  const text: RubyParser.TextParser = input => {
66
67
  const { context } = input;
67
68
  const { source } = context;
68
- const acc = [''];
69
+ const acc = new List([new Data('')]);
69
70
  let state = false;
70
71
  context.sequential = true;
71
72
  for (let { position } = context; position < source.length; position = context.position) {
@@ -75,41 +76,37 @@ const text: RubyParser.TextParser = input => {
75
76
  case '&': {
76
77
  const result = unsafehtmlentity(input) ?? txt(input)!;
77
78
  assert(result);
78
- acc[acc.length - 1] += eval(result)[0];
79
+ acc.last!.value += eval(result).head!.value;
79
80
  continue;
80
81
  }
81
82
  default: {
82
83
  if (source[position].trimStart() === '') {
83
- state ||= acc.at(-1)!.trimStart() !== '';
84
- acc.push('');
84
+ state ||= acc.last!.value.trimStart() !== '';
85
+ acc.push(new Data(''));
85
86
  context.position += 1;
86
87
  continue;
87
88
  }
88
89
  const result = txt(input)!;
89
90
  assert(result);
90
- acc[acc.length - 1] += eval(result)[0] ?? source.slice(position, context.position);
91
+ acc.last!.value += eval(result).head?.value ?? '';
91
92
  continue;
92
93
  }
93
94
  }
94
95
  }
95
96
  context.sequential = false;
96
- state ||= acc.at(-1)!.trimStart() !== '';
97
+ state ||= acc.last!.value.trimStart() !== '';
97
98
  return state
98
- ? [acc]
99
+ ? acc
99
100
  : undefined;
100
101
  };
101
102
 
102
- //function attributes(texts: string[], rubies: string[]): Record<string, string> {
103
- // let attrs: Record<string, string> | undefined;
104
- // for (const ss of [texts, rubies]) {
105
- // for (let i = 0; i < ss.length; ++i) {
106
- // if (!ss[i].includes(Command.Error)) continue;
107
- // ss[i] = ss[i].replace(CmdRegExp.Error, '');
108
- // attrs ??= {
109
- // class: 'invalid',
110
- // ...invalid('ruby', ss === texts ? 'content' : 'argument', 'Invalid HTML entity'),
111
- // };
112
- // }
113
- // }
114
- // return attrs ?? {};
115
- //}
103
+ function* zip<N extends List.Node>(a: List<N>, b: List<N>): Iterable<[N | undefined, N | undefined]> {
104
+ const ia = a[Symbol.iterator]();
105
+ const ib = b[Symbol.iterator]();
106
+ while (true) {
107
+ const ra = ia.next();
108
+ const rb = ib.next();
109
+ if (ra.done) break;
110
+ yield [ra.value, rb.value];
111
+ }
112
+ }
@@ -1,11 +1,12 @@
1
1
  import { StrongParser } from '../inline';
2
2
  import { Recursion } from '../context';
3
+ import { List, Data } from '../../combinator/data/parser';
3
4
  import { union, some, recursion, precedence, surround, open, lazy } from '../../combinator';
4
5
  import { inline } from '../inline';
5
6
  import { emphasis } from './emphasis';
6
7
  import { str } from '../source';
7
8
  import { tightStart, blankWith } from '../visibility';
8
- import { unshift } from 'spica/array';
9
+ import { unwrap } from '../util';
9
10
  import { html, defrag } from 'typed-dom/dom';
10
11
 
11
12
  export const strong: StrongParser = lazy(() => surround(
@@ -17,5 +18,5 @@ export const strong: StrongParser = lazy(() => surround(
17
18
  open(some(inline, '*'), inline),
18
19
  ]))))),
19
20
  str('**'), false,
20
- ([, bs]) => [[html('strong', defrag(bs))]],
21
- ([as, bs]) => bs && [unshift(as, bs)]));
21
+ ([, bs]) => new List([new Data(html('strong', defrag(unwrap(bs))))]),
22
+ ([as, bs]) => bs && as.import(bs as List<Data<string>>)));
@@ -1,9 +1,9 @@
1
1
  import { TemplateParser } from '../inline';
2
2
  import { Recursion, Backtrack } from '../context';
3
+ import { List, Data } from '../../combinator/data/parser';
3
4
  import { union, some, recursion, precedence, surround, lazy } from '../../combinator';
4
5
  import { escsource, str } from '../source';
5
- import { invalid } from '../util';
6
- import { unshift, push } from 'spica/array';
6
+ import { unwrap, invalid } from '../util';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
 
9
9
  export const template: TemplateParser = lazy(() => surround(
@@ -12,35 +12,36 @@ export const template: TemplateParser = lazy(() => surround(
12
12
  some(union([bracket, escsource]), '}')),
13
13
  str('}}'),
14
14
  true,
15
- ([as, bs = [], cs]) =>
16
- [[html('span', { class: 'template' }, defrag(push(unshift(as, bs), cs)))]],
15
+ ([as, bs = new List(), cs]) => new List([
16
+ new Data(html('span', { class: 'template' }, defrag(unwrap(as.import(bs as List<Data<string>>).import(cs)))))
17
+ ]),
17
18
  ([, bs], context) =>
18
- bs && [[
19
- html('span',
19
+ bs && new List([
20
+ new Data(html('span',
20
21
  {
21
22
  class: 'invalid',
22
23
  ...invalid('template', 'syntax', `Missing the closing symbol "}}"`),
23
24
  },
24
- context.source.slice(context.position - context.range!, context.position))
25
- ]],
25
+ context.source.slice(context.position - context.range!, context.position)))
26
+ ]),
26
27
  [3 | Backtrack.doublebracket, 3 | Backtrack.escbracket]));
27
28
 
28
29
  const bracket: TemplateParser.BracketParser = lazy(() => union([
29
30
  surround(str('('), recursion(Recursion.terminal, some(union([bracket, escsource]), ')')), str(')'), true,
30
- undefined, () => [[]], [3 | Backtrack.escbracket]),
31
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
31
32
  surround(str('['), recursion(Recursion.terminal, some(union([bracket, escsource]), ']')), str(']'), true,
32
- undefined, () => [[]], [3 | Backtrack.escbracket]),
33
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
33
34
  surround(str('{'), recursion(Recursion.terminal, some(union([bracket, escsource]), '}')), str('}'), true,
34
- undefined, () => [[]], [3 | Backtrack.escbracket]),
35
+ undefined, () => new List(), [3 | Backtrack.escbracket]),
35
36
  surround(
36
37
  str('"'),
37
38
  precedence(2, recursion(Recursion.terminal, some(escsource, /["\n]/y, [['"', 2], ['\n', 3]]))),
38
39
  str('"'),
39
40
  true,
40
- ([as, bs = [], cs], context) =>
41
+ ([as, bs = new List(), cs], context) =>
41
42
  context.linebreak === 0
42
- ? [push(unshift(as, bs), cs)]
43
- : (context.position -= 1, [unshift(as, bs)]),
44
- ([as, bs]) => bs && [unshift(as, bs)],
43
+ ? as.import(bs as List<Data<string>>).import(cs)
44
+ : (context.position -= 1, as.import(bs as List<Data<string>>)),
45
+ ([as, bs]) => bs && as.import(bs as List<Data<string>>),
45
46
  [3 | Backtrack.escbracket]),
46
47
  ]));
@@ -56,9 +56,9 @@ describe('Unit: parser/inline', () => {
56
56
  assert.deepStrictEqual(inspect(parser('**a*b***'), ctx), [['<strong>a<em>b</em></strong>'], '']);
57
57
  assert.deepStrictEqual(inspect(parser('**a*b***c'), ctx), [['<strong>a<em>b</em></strong>', 'c'], '']);
58
58
  assert.deepStrictEqual(inspect(parser('**a**b****'), ctx), [['<strong>a</strong>', 'b', '****'], '']);
59
- assert.deepStrictEqual(inspect(parser('**a**b****c'), ctx), [['<strong>a</strong>', 'b', '****', 'c'], '']);
59
+ assert.deepStrictEqual(inspect(parser('**a**b****c'), ctx), [['<strong>a</strong>', 'b', '****c'], '']);
60
60
  assert.deepStrictEqual(inspect(parser('**a***b*****'), ctx), [['<strong>a</strong>', '<em>b</em>', '****'], '']);
61
- assert.deepStrictEqual(inspect(parser('**a***b*****c'), ctx), [['<strong>a</strong>', '<em>b</em>', '****', 'c'], '']);
61
+ assert.deepStrictEqual(inspect(parser('**a***b*****c'), ctx), [['<strong>a</strong>', '<em>b</em>', '****c'], '']);
62
62
  assert.deepStrictEqual(inspect(parser('**a *b***'), ctx), [['<strong>a <em>b</em></strong>'], '']);
63
63
  assert.deepStrictEqual(inspect(parser('**a *b***c'), ctx), [['<strong>a <em>b</em></strong>', 'c'], '']);
64
64
  assert.deepStrictEqual(inspect(parser('**a **b****'), ctx), [['<strong>a <strong>b</strong></strong>'], '']);
@@ -68,7 +68,7 @@ describe('Unit: parser/inline', () => {
68
68
  assert.deepStrictEqual(inspect(parser('***a*'), ctx), [['**', '<em>a</em>'], '']);
69
69
  assert.deepStrictEqual(inspect(parser('***a*b'), ctx), [['**', '<em>a</em>', 'b'], '']);
70
70
  assert.deepStrictEqual(inspect(parser('***a*b*'), ctx), [['**', '<em>a</em>', 'b', '*'], '']);
71
- assert.deepStrictEqual(inspect(parser('***a*b*c'), ctx), [['**', '<em>a</em>', 'b*c'], '']);
71
+ assert.deepStrictEqual(inspect(parser('***a*b*c'), ctx), [['**', '<em>a</em>', 'b', '*', 'c'], '']);
72
72
  assert.deepStrictEqual(inspect(parser('***a*b*c*'), ctx), [['**', '<em>a</em>', 'b', '<em>c</em>'], '']);
73
73
  assert.deepStrictEqual(inspect(parser('***a*b*c**'), ctx), [['**', '<em>a</em>', 'b', '<em>c</em>', '*'], '']);
74
74
  assert.deepStrictEqual(inspect(parser('***a*b*c***'), ctx), [['<strong><em>a</em>b<em>c</em></strong>'], '']);
@@ -53,7 +53,9 @@ export function* segment(source: string): Generator<string, undefined, undefined
53
53
  const result = parser(input)!;
54
54
  assert(result);
55
55
  assert(context.position > position);
56
- const segs = eval(result).length ? eval(result) : [source.slice(position, context.position)];
56
+ const segs = eval(result).length > 0
57
+ ? eval(result).foldl<string[]>((acc, { value }) => void acc.push(value) || acc, [])
58
+ : [source.slice(position, context.position)];
57
59
  assert(segs.join('') === source.slice(position, context.position));
58
60
  for (let i = 0; i < segs.length; ++i) {
59
61
  const seg = segs[i];
@@ -1,5 +1,6 @@
1
1
  import { EscapableSourceParser } from '../source';
2
2
  import { Command } from '../context';
3
+ import { List, Data } from '../../combinator/data/parser';
3
4
  import { consume } from '../../combinator';
4
5
  import { next } from './text';
5
6
  import { html } from 'typed-dom/dom';
@@ -16,33 +17,33 @@ export const escsource: EscapableSourceParser = ({ context }) => {
16
17
  case '\r':
17
18
  assert(!source.includes('\r', position + 1));
18
19
  consume(-1, context);
19
- return [[]];
20
+ return new List();
20
21
  case Command.Escape:
21
22
  consume(1, context);
22
23
  context.position += 1;
23
- return [[source.slice(position + 1, position + 2)]];
24
+ return new List([new Data(source.slice(position + 1, position + 2))]);
24
25
  case '\\':
25
26
  switch (source[position + 1]) {
26
27
  case undefined:
27
- return [[char]];
28
+ return new List([new Data(char)]);
28
29
  case '\n':
29
- return [[char]];
30
+ return new List([new Data(char)]);
30
31
  default:
31
32
  consume(1, context);
32
33
  context.position += 1;
33
- return [[source.slice(position, position + 2)]];
34
+ return new List([new Data(source.slice(position, position + 2))]);
34
35
  }
35
36
  case '\n':
36
37
  context.linebreak ||= source.length - position;
37
- return [[html('br')]];
38
+ return new List([new Data(html('br'))]);
38
39
  default:
39
40
  assert(char !== '\n');
40
- if (context.sequential) return [[char]];
41
+ if (context.sequential) return new List([new Data(char)]);
41
42
  let i = next(source, position, delimiter);
42
43
  assert(i > position);
43
44
  i -= position;
44
45
  consume(i - 1, context);
45
46
  context.position += i - 1;
46
- return [[source.slice(position, context.position)]];
47
+ return new List([new Data(source.slice(position, context.position))]);
47
48
  }
48
49
  };