securemark 0.256.0 → 0.257.2

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 (55) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +21 -8
  3. package/dist/index.js +237 -197
  4. package/markdown.d.ts +23 -17
  5. package/package.json +1 -1
  6. package/src/combinator/control/manipulation/context.ts +13 -2
  7. package/src/combinator/control/manipulation/resource.ts +36 -2
  8. package/src/combinator/control/manipulation/surround.ts +6 -6
  9. package/src/combinator/data/parser/inits.ts +1 -1
  10. package/src/combinator/data/parser/sequence.ts +1 -1
  11. package/src/combinator/data/parser/some.ts +16 -38
  12. package/src/combinator/data/parser.ts +34 -18
  13. package/src/debug.test.ts +2 -2
  14. package/src/parser/api/bind.ts +9 -11
  15. package/src/parser/api/parse.test.ts +48 -11
  16. package/src/parser/block.ts +1 -1
  17. package/src/parser/inline/annotation.test.ts +7 -5
  18. package/src/parser/inline/annotation.ts +10 -6
  19. package/src/parser/inline/autolink/account.ts +3 -7
  20. package/src/parser/inline/autolink/anchor.ts +3 -7
  21. package/src/parser/inline/autolink/hashnum.ts +3 -7
  22. package/src/parser/inline/autolink/hashtag.ts +3 -7
  23. package/src/parser/inline/autolink/url.test.ts +1 -0
  24. package/src/parser/inline/autolink/url.ts +7 -8
  25. package/src/parser/inline/bracket.test.ts +13 -7
  26. package/src/parser/inline/bracket.ts +10 -10
  27. package/src/parser/inline/comment.test.ts +5 -3
  28. package/src/parser/inline/comment.ts +4 -4
  29. package/src/parser/inline/deletion.ts +3 -3
  30. package/src/parser/inline/emphasis.ts +3 -3
  31. package/src/parser/inline/emstrong.ts +4 -5
  32. package/src/parser/inline/extension/index.test.ts +1 -0
  33. package/src/parser/inline/extension/index.ts +8 -7
  34. package/src/parser/inline/extension/indexer.ts +3 -5
  35. package/src/parser/inline/extension/label.ts +1 -1
  36. package/src/parser/inline/extension/placeholder.test.ts +1 -0
  37. package/src/parser/inline/extension/placeholder.ts +4 -4
  38. package/src/parser/inline/html.test.ts +2 -0
  39. package/src/parser/inline/html.ts +5 -5
  40. package/src/parser/inline/insertion.ts +3 -3
  41. package/src/parser/inline/link.test.ts +1 -0
  42. package/src/parser/inline/link.ts +58 -17
  43. package/src/parser/inline/mark.ts +3 -3
  44. package/src/parser/inline/math.test.ts +21 -14
  45. package/src/parser/inline/math.ts +7 -19
  46. package/src/parser/inline/media.test.ts +0 -2
  47. package/src/parser/inline/media.ts +10 -10
  48. package/src/parser/inline/reference.test.ts +6 -5
  49. package/src/parser/inline/reference.ts +12 -8
  50. package/src/parser/inline/ruby.ts +29 -27
  51. package/src/parser/inline/strong.ts +3 -3
  52. package/src/parser/inline/template.ts +4 -4
  53. package/src/parser/inline.test.ts +13 -8
  54. package/src/parser/inline.ts +1 -0
  55. package/src/parser/util.ts +35 -19
@@ -1,12 +1,12 @@
1
1
  import { undefined, location, encodeURI, decodeURI, Location } from 'spica/global';
2
- import { LinkParser } from '../inline';
3
- import { eval } from '../../combinator/data/parser';
4
- import { union, inits, tails, some, validate, guard, context, creator, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
2
+ import { LinkParser, TextLinkParser } from '../inline';
3
+ import { Result, eval } from '../../combinator/data/parser';
4
+ import { union, inits, tails, subsequence, some, validate, guard, context, precedence, creator, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
5
5
  import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
7
7
  import { autolink } from '../autolink';
8
- import { str } from '../source';
9
- import { trimBlankStart, trimNodeEnd, stringify } from '../util';
8
+ import { unescsource, str } from '../source';
9
+ import { trimNode, stringify } from '../util';
10
10
  import { html, define, defrag } from 'typed-dom/dom';
11
11
  import { ReadonlyURL } from 'spica/url';
12
12
 
@@ -15,9 +15,9 @@ const optspec = {
15
15
  } as const;
16
16
  Object.setPrototypeOf(optspec, null);
17
17
 
18
- export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], bind(
18
+ export const link: LinkParser = lazy(() => validate(['[', '{'], creator(10, precedence(2, bind(
19
19
  guard(context => context.syntax?.inline?.link ?? true,
20
- reverse(tails([
20
+ fmap(subsequence([
21
21
  context({ syntax: { inline: {
22
22
  link: false,
23
23
  }}},
@@ -36,15 +36,22 @@ export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], bind
36
36
  media: false,
37
37
  autolink: false,
38
38
  }}},
39
- trimBlankStart(some(inline, ']', /^\\?\n/))),
39
+ some(inline, ']', [[/^\\?\n/, 9], [']', 2]])),
40
40
  ']',
41
- true),
41
+ true,
42
+ undefined,
43
+ ([, ns = [], rest], next) => next[0] === ']' ? undefined : optimize('[', ns, rest)),
42
44
  ]))),
45
+ // 全体の失敗が確定した時も解析し予算を浪費している
43
46
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
44
- ]))),
45
- ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
47
+ ]),
48
+ ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []])),
49
+ ([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
50
+ if (params.length === 0) return;
51
+ assert(content[0] !== '' || params.length === 0);
52
+ if (content[0] === '') return [content, rest];
46
53
  assert(params.every(p => typeof p === 'string'));
47
- content = trimNodeEnd(content);
54
+ if (content.length !== 0 && trimNode(content).length === 0) return;
48
55
  if (eval(some(autolink)(stringify(content), context))?.some(node => typeof node === 'object')) return;
49
56
  assert(!html('div', content).querySelector('a, .media, .annotation, .reference') || (content[0] as HTMLElement).matches('.media'));
50
57
  const INSECURE_URI = params.shift()!;
@@ -60,12 +67,37 @@ export const link: LinkParser = lazy(() => creator(10, validate(['[', '{'], bind
60
67
  if (el.classList.contains('invalid')) return [[el], rest];
61
68
  assert(el.classList.length === 0);
62
69
  return [[define(el, attributes('link', [], optspec, params))], rest];
63
- }))));
70
+ })))));
64
71
 
65
- export const uri: LinkParser.ParameterParser.UriParser = union([
72
+ export const textlink: TextLinkParser = lazy(() => validate(['[', '{'], creator(10, precedence(2, bind(
73
+ reverse(tails([
74
+ dup(surround('[', some(union([unescsource]), ']'), ']')),
75
+ dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
76
+ ])),
77
+ ([params, content = []], rest, context) => {
78
+ assert(params[0] === '\r');
79
+ params.shift();
80
+ assert(params.every(p => typeof p === 'string'));
81
+ trimNode(content);
82
+ const INSECURE_URI = params.shift()!;
83
+ assert(INSECURE_URI === INSECURE_URI.trim());
84
+ assert(!INSECURE_URI.match(/\s/));
85
+ const el = elem(
86
+ INSECURE_URI,
87
+ defrag(content),
88
+ new ReadonlyURL(
89
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
90
+ context.host?.href || location.href),
91
+ context.host?.origin || location.origin);
92
+ assert(!el.classList.contains('invalid'));
93
+ assert(el.classList.length === 0);
94
+ return [[define(el, attributes('link', [], optspec, params))], rest];
95
+ })))));
96
+
97
+ export const uri: LinkParser.ParameterParser.UriParser = fmap(union([
66
98
  open(/^[^\S\n]+/, str(/^\S+/)),
67
99
  str(/^[^\s{}]+/),
68
- ]);
100
+ ]), ([uri]) => ['\r', uri]);
69
101
 
70
102
  export const option: LinkParser.ParameterParser.OptionParser = union([
71
103
  fmap(str(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]),
@@ -90,8 +122,8 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
90
122
  default:
91
123
  const target = new ReadonlyURL(uri, source.href);
92
124
  return target.origin === host.origin
93
- ? target.href.slice(target.origin.length)
94
- : target.href;
125
+ ? target.href.slice(target.origin.length)
126
+ : target.href;
95
127
  }
96
128
  }
97
129
 
@@ -163,3 +195,12 @@ function decode(uri: string): string {
163
195
  return uri.replace(/\s+/g, encodeURI);
164
196
  }
165
197
  }
198
+
199
+ export function optimize(opener: string, ns: readonly (string | HTMLElement)[], rest: string): Result<string> {
200
+ let count = 0;
201
+ for (let i = 0; i < ns.length - 1; i += 2) {
202
+ if (ns[i] !== '' || ns[i + 1] !== opener[0]) break;
203
+ ++count;
204
+ }
205
+ return [['', opener[0].repeat(opener.length + count)], rest.slice(count)];
206
+ }
@@ -1,12 +1,12 @@
1
1
  import { MarkParser } from '../inline';
2
- import { union, some, creator, surround, open, lazy } from '../../combinator';
2
+ import { union, some, precedence, creator, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { str } from '../source';
5
5
  import { startTight, blankWith } from '../util';
6
6
  import { html, defrag } from 'typed-dom/dom';
7
7
  import { unshift } from 'spica/array';
8
8
 
9
- export const mark: MarkParser = lazy(() => creator(surround(
9
+ export const mark: MarkParser = lazy(() => creator(precedence(1, surround(
10
10
  str('=='),
11
11
  startTight(some(union([
12
12
  some(inline, blankWith('==')),
@@ -14,4 +14,4 @@ export const mark: MarkParser = lazy(() => creator(surround(
14
14
  ]))),
15
15
  str('=='), false,
16
16
  ([, bs], rest) => [[html('mark', defrag(bs))], rest],
17
- ([as, bs], rest) => [unshift(as, bs), rest])));
17
+ ([as, bs], rest) => [unshift(as, bs), rest]))));
@@ -18,6 +18,12 @@ describe('Unit: parser/inline/math', () => {
18
18
  assert.deepStrictEqual(inspect(parser('$-a $-b')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('$-a\\ $-b')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('$a $')), undefined);
21
+ assert.deepStrictEqual(inspect(parser('$-a `$`')), undefined);
22
+ assert.deepStrictEqual(inspect(parser('$-a ``$``')), undefined);
23
+ // (``" + ``" + $-b") or (``"`` + "$-b")
24
+ assert.deepStrictEqual(inspect(parser('$-a ``"``"$-b"')), undefined);
25
+ // (``b`` + "c" + $-d) or (``b``"c" + $-d)
26
+ assert.deepStrictEqual(inspect(parser('$-a ``b``"c"$-d')), undefined);
21
27
  assert.deepStrictEqual(inspect(parser('$-a"$-b"')), undefined);
22
28
  assert.deepStrictEqual(inspect(parser('$-a\\"$-b\\"')), undefined);
23
29
  assert.deepStrictEqual(inspect(parser('$a"$')), undefined);
@@ -28,7 +34,6 @@ describe('Unit: parser/inline/math', () => {
28
34
  assert.deepStrictEqual(inspect(parser('$-a\\[$-b\\]')), undefined);
29
35
  assert.deepStrictEqual(inspect(parser('$a[$')), undefined);
30
36
  assert.deepStrictEqual(inspect(parser('$-a{$-b}')), undefined);
31
- assert.deepStrictEqual(inspect(parser('$-a\\{$-b\\}')), undefined);
32
37
  assert.deepStrictEqual(inspect(parser('$a{$')), undefined);
33
38
  assert.deepStrictEqual(inspect(parser('$a$b')), undefined);
34
39
  assert.deepStrictEqual(inspect(parser('$a$b$')), undefined);
@@ -38,13 +43,25 @@ describe('Unit: parser/inline/math', () => {
38
43
  assert.deepStrictEqual(inspect(parser('$\n$')), undefined);
39
44
  assert.deepStrictEqual(inspect(parser('$a\\$\nb$')), undefined);
40
45
  assert.deepStrictEqual(inspect(parser('$a\\$\\\nb$')), undefined);
41
- assert.deepStrictEqual(inspect(parser('$`"$')), undefined);
46
+ assert.deepStrictEqual(inspect(parser('$`$')), undefined);
47
+ assert.deepStrictEqual(inspect(parser('$`a"$')), undefined);
48
+ assert.deepStrictEqual(inspect(parser('$`a"b$')), undefined);
49
+ assert.deepStrictEqual(inspect(parser('$``$')), undefined);
50
+ assert.deepStrictEqual(inspect(parser('$``$"$')), undefined);
51
+ assert.deepStrictEqual(inspect(parser('$``a$')), undefined);
42
52
  assert.deepStrictEqual(inspect(parser('$``a"$0')), undefined);
53
+ assert.deepStrictEqual(inspect(parser('$``a""b$')), undefined);
43
54
  assert.deepStrictEqual(inspect(parser('$``a""$')), undefined);
55
+ assert.deepStrictEqual(inspect(parser('$```a"$')), undefined);
44
56
  assert.deepStrictEqual(inspect(parser('$```a""$')), undefined);
45
57
  assert.deepStrictEqual(inspect(parser('$``\n"$')), undefined);
46
58
  assert.deepStrictEqual(inspect(parser('$``a\\$\nb"$')), undefined);
47
59
  assert.deepStrictEqual(inspect(parser('$``a\\$\\\nb"$')), undefined);
60
+ assert.deepStrictEqual(inspect(parser('$``"$')), undefined);
61
+ assert.deepStrictEqual(inspect(parser('$``a"$')), undefined);
62
+ assert.deepStrictEqual(inspect(parser('$``\\"$')), undefined);
63
+ assert.deepStrictEqual(inspect(parser('$``a``b"c"$')), undefined);
64
+ assert.deepStrictEqual(inspect(parser('$````""$')), undefined);
48
65
  assert.deepStrictEqual(inspect(parser('$"$')), undefined);
49
66
  assert.deepStrictEqual(inspect(parser('$}$')), undefined);
50
67
  assert.deepStrictEqual(inspect(parser('${')), undefined);
@@ -104,18 +121,8 @@ describe('Unit: parser/inline/math', () => {
104
121
  assert.deepStrictEqual(inspect(parser('$f(x)$')), [['<span class="math" translate="no" data-src="$f(x)$">$f(x)$</span>'], '']);
105
122
  assert.deepStrictEqual(inspect(parser('$f: x \\to y$')), [['<span class="math" translate="no" data-src="$f: x \\to y$">$f: x \\to y$</span>'], '']);
106
123
  assert.deepStrictEqual(inspect(parser('$k$-space')), [['<span class="math" translate="no" data-src="$k$">$k$</span>'], '-space']);
107
- assert.deepStrictEqual(inspect(parser('$`$')), [['<span class="math" translate="no" data-src="$`$">$`$</span>'], '']);
108
- assert.deepStrictEqual(inspect(parser('$`"a$')), [['<span class="math" translate="no" data-src="$`&quot;a$">$`"a$</span>'], '']);
109
- assert.deepStrictEqual(inspect(parser('$``$')), [['<span class="math" translate="no" data-src="$``$">$``$</span>'], '']);
110
- assert.deepStrictEqual(inspect(parser('$``"$')), [['<span class="math" translate="no" data-src="$``&quot;$">$``"$</span>'], '']);
111
- assert.deepStrictEqual(inspect(parser('$``""a$')), [['<span class="math" translate="no" data-src="$``&quot;&quot;a$">$``""a$</span>'], '']);
112
- assert.deepStrictEqual(inspect(parser('$``a"$')), [['<span class="math" translate="no" data-src="$``a&quot;$">$``a"$</span>'], '']);
113
- assert.deepStrictEqual(inspect(parser('$``$"$')), [['<span class="math" translate="no" data-src="$``$">$``$</span>'], '"$']);
114
- assert.deepStrictEqual(inspect(parser('$``\\"$')), [['<span class="math" translate="no" data-src="$``\\&quot;$">$``\\"$</span>'], '']);
115
- assert.deepStrictEqual(inspect(parser('$``a``b"c"$')), [['<span class="math" translate="no" data-src="$``a``b&quot;c&quot;$">$``a``b"c"$</span>'], '']);
116
- assert.deepStrictEqual(inspect(parser('$```"$')), [['<span class="math" translate="no" data-src="$```&quot;$">$```"$</span>'], '']);
117
- assert.deepStrictEqual(inspect(parser('$````""$')), [['<span class="math" translate="no" data-src="$````&quot;&quot;$">$````""$</span>'], '']);
118
124
  assert.deepStrictEqual(inspect(parser('$a{b}$')), [['<span class="math" translate="no" data-src="$a{b}$">$a{b}$</span>'], '']);
125
+ assert.deepStrictEqual(inspect(parser('$a\\{$\\}')), [['<span class="math" translate="no" data-src="$a\\{$">$a\\{$</span>'], '\\}']);
119
126
  assert.deepStrictEqual(inspect(parser('${}$')), [['<span class="math" translate="no" data-src="${}$">${}$</span>'], '']);
120
127
  assert.deepStrictEqual(inspect(parser('${ }$')), [['<span class="math" translate="no" data-src="${ }$">${ }$</span>'], '']);
121
128
  assert.deepStrictEqual(inspect(parser('${a}$')), [['<span class="math" translate="no" data-src="${a}$">${a}$</span>'], '']);
@@ -127,7 +134,7 @@ describe('Unit: parser/inline/math', () => {
127
134
  assert.deepStrictEqual(inspect(parser('${a }$')), [['<span class="math" translate="no" data-src="${a }$">${a }$</span>'], '']);
128
135
  assert.deepStrictEqual(inspect(parser('${ a}$')), [['<span class="math" translate="no" data-src="${ a}$">${ a}$</span>'], '']);
129
136
  assert.deepStrictEqual(inspect(parser('${ a }$')), [['<span class="math" translate="no" data-src="${ a }$">${ a }$</span>'], '']);
130
- assert.deepStrictEqual(inspect(parser('${``a"}$')), [['<span class="math" translate="no" data-src="${``a&quot;}$">${``a"}$</span>'], '']);
137
+ assert.deepStrictEqual(inspect(parser('${``"}$')), [['<span class="math" translate="no" data-src="${``&quot;}$">${``"}$</span>'], '']);
131
138
  assert.deepStrictEqual(inspect(parser('${\\a}$')), [['<span class="math" translate="no" data-src="${\\a}$">${\\a}$</span>'], '']);
132
139
  assert.deepStrictEqual(inspect(parser('${\\$}$')), [['<span class="math" translate="no" data-src="${\\$}$">${\\$}$</span>'], '']);
133
140
  assert.deepStrictEqual(inspect(parser('${\\\\}$')), [['<span class="math" translate="no" data-src="${\\\\}$">${\\\\}$</span>'], '']);
@@ -1,21 +1,19 @@
1
1
  import { MathParser } from '../inline';
2
- import { union, some, validate, focus, rewrite, creator, surround, lazy } from '../../combinator';
3
- import { escsource, str } from '../source';
2
+ import { union, some, validate, focus, rewrite, precedence, creator, surround, lazy } from '../../combinator';
3
+ import { escsource, unescsource } from '../source';
4
4
  import { html } from 'typed-dom/dom';
5
5
 
6
- const syntax = /^(?:[ "([](?!\$)|\\{(?!\$)|\\[\\}$]?|^`|`(?!`)|[!#%&')\x2A-\x5A\]^_\x61-\x7A|~])+/;
7
6
  const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])/i;
8
7
 
9
- export const math: MathParser = lazy(() => creator(validate('$', rewrite(
8
+ export const math: MathParser = lazy(() => validate('$', creator(rewrite(
10
9
  union([
11
- surround('$', bracket, '$'),
10
+ surround('$', precedence(6, bracket), '$'),
12
11
  surround(
13
12
  /^\$(?![\s{}])/,
14
- some(union([
13
+ precedence(3, some(union([
15
14
  bracket,
16
- quote,
17
- str(syntax),
18
- ])),
15
+ focus(/^(?:[ ([](?!\$)|\\[\\{}$]?|[!#%&')\x2A-\x5A\]^_\x61-\x7A|~])+/, some(unescsource)),
16
+ ]))),
19
17
  /^\$(?![0-9A-Za-z])/),
20
18
  ]),
21
19
  (source, { caches: { math: cache } = {} }) => [[
@@ -41,13 +39,3 @@ const bracket: MathParser.BracketParser = lazy(() => creator(surround(
41
39
  ])),
42
40
  '}',
43
41
  true)));
44
-
45
- const quote: MathParser.QuoteParser = lazy(() => creator(surround(
46
- '``',
47
- some(union([
48
- quote,
49
- bracket,
50
- focus(/^(?:\\[\\{}$]|`(?!`)|[^`{}"$\n\P{ASCII}])*/u, str(syntax)),
51
- ])),
52
- /^"?/,
53
- true)));
@@ -45,8 +45,6 @@ describe('Unit: parser/inline/media', () => {
45
45
  assert.deepStrictEqual(inspect(parser('![[]{b}')), undefined);
46
46
  assert.deepStrictEqual(inspect(parser('![]]{b}')), undefined);
47
47
  assert.deepStrictEqual(inspect(parser('![a]{}')), undefined);
48
- assert.deepStrictEqual(inspect(parser('![\\ a ]{b}')), undefined);
49
- assert.deepStrictEqual(inspect(parser('![ \\ a ]{b}')), undefined);
50
48
  assert.deepStrictEqual(inspect(parser('![a\nb]{b}')), undefined);
51
49
  assert.deepStrictEqual(inspect(parser('![a\\\nb]{b}')), undefined);
52
50
  assert.deepStrictEqual(inspect(parser('![]{ttp://host}')), [['<img class="media invalid" data-src="ttp://host" alt="">'], '']);
@@ -1,13 +1,13 @@
1
1
  import { undefined, location } from 'spica/global';
2
2
  import { MediaParser } from '../inline';
3
- import { union, inits, tails, some, validate, verify, guard, creator, surround, open, dup, lazy, fmap, bind } from '../../combinator';
4
- import { link, uri, option as linkoption, resolve } from './link';
3
+ import { union, inits, tails, some, validate, verify, guard, precedence, creator, surround, open, dup, lazy, fmap, bind } from '../../combinator';
4
+ import { textlink, uri, option as linkoption, resolve } from './link';
5
5
  import { attributes } from './html';
6
6
  import { unsafehtmlentity } from './htmlentity';
7
7
  import { txt, str } from '../source';
8
8
  import { html, define } from 'typed-dom/dom';
9
9
  import { ReadonlyURL } from 'spica/url';
10
- import { unshift, push } from 'spica/array';
10
+ import { unshift, shift, push } from 'spica/array';
11
11
 
12
12
  const optspec = {
13
13
  'width': [],
@@ -17,18 +17,18 @@ const optspec = {
17
17
  } as const;
18
18
  Object.setPrototypeOf(optspec, null);
19
19
 
20
- export const media: MediaParser = lazy(() => creator(10, validate(['![', '!{'], bind(verify(fmap(open(
20
+ export const media: MediaParser = lazy(() => validate(['![', '!{'], creator(10, precedence(2, bind(verify(fmap(open(
21
21
  '!',
22
22
  guard(context => context.syntax?.inline?.media ?? true,
23
23
  tails([
24
24
  dup(surround(
25
- /^\[(?!\s*\\\s)/,
26
- some(union([unsafehtmlentity, bracket, txt]), ']', /^\\?\n/),
25
+ '[',
26
+ some(union([unsafehtmlentity, bracket, txt]), ']', [[/^\\?\n/, 9]]),
27
27
  ']',
28
28
  true)),
29
29
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
30
30
  ]))),
31
- ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]),
31
+ ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], shift(bs)[1]] : [[''], shift(as)[1]]),
32
32
  ([[text]]) => text === '' || text.trim() !== ''),
33
33
  ([[text], params], rest, context) => {
34
34
  assert(text === text.trim());
@@ -54,16 +54,16 @@ export const media: MediaParser = lazy(() => creator(10, validate(['![', '!{'],
54
54
  }
55
55
  if (context.syntax?.inline?.link === false || cache && cache.tagName !== 'IMG') return [[el], rest];
56
56
  return fmap(
57
- link as MediaParser,
57
+ textlink as MediaParser,
58
58
  ([link]) => [define(link, { target: '_blank' }, [el])])
59
59
  (`{ ${INSECURE_URI}${params.join('')} }${rest}`, context);
60
- }))));
60
+ })))));
61
61
 
62
62
  const bracket: MediaParser.TextParser.BracketParser = lazy(() => union([
63
63
  surround(str('('), some(union([unsafehtmlentity, bracket, txt]), ')'), str(')'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
64
64
  surround(str('['), some(union([unsafehtmlentity, bracket, txt]), ']'), str(']'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
65
65
  surround(str('{'), some(union([unsafehtmlentity, bracket, txt]), '}'), str('}'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
66
- surround(str('"'), some(union([unsafehtmlentity, txt]), '"'), str('"'), true),
66
+ surround(str('"'), precedence(8, some(union([unsafehtmlentity, txt]), '"')), str('"'), true),
67
67
  ]));
68
68
 
69
69
  const option: MediaParser.ParameterParser.OptionParser = union([
@@ -14,14 +14,15 @@ describe('Unit: parser/inline/reference', () => {
14
14
  assert.deepStrictEqual(inspect(parser('[[]]')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('[[]]]')), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('[[ ]]')), undefined);
17
+ assert.deepStrictEqual(inspect(parser('[[ [a')), [['', '[['], ' [a']);
17
18
  assert.deepStrictEqual(inspect(parser('[[\n]]')), undefined);
18
19
  assert.deepStrictEqual(inspect(parser('[[\na]]')), undefined);
19
20
  assert.deepStrictEqual(inspect(parser('[[\\\na]]')), undefined);
20
- assert.deepStrictEqual(inspect(parser('[[a\n]]')), undefined);
21
- assert.deepStrictEqual(inspect(parser('[[a\\\n]]')), undefined);
22
- assert.deepStrictEqual(inspect(parser('[[a\nb]]')), undefined);
23
- assert.deepStrictEqual(inspect(parser('[[a\\\nb]]')), undefined);
24
- assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), undefined);
21
+ assert.deepStrictEqual(inspect(parser('[[a\n]]')), [['', '[['], 'a\n]]']);
22
+ assert.deepStrictEqual(inspect(parser('[[a\\\n]]')), [['', '[['], 'a\\\n]]']);
23
+ assert.deepStrictEqual(inspect(parser('[[a\nb]]')), [['', '[['], 'a\nb]]']);
24
+ assert.deepStrictEqual(inspect(parser('[[a\\\nb]]')), [['', '[['], 'a\\\nb]]']);
25
+ assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['', '[['], '*a\nb*]]']);
25
26
  assert.deepStrictEqual(inspect(parser('[[\\]]')), undefined);
26
27
  assert.deepStrictEqual(inspect(parser('[[a]b]]')), undefined);
27
28
  assert.deepStrictEqual(inspect(parser('[[[a]]')), undefined);
@@ -1,14 +1,16 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { ReferenceParser } from '../inline';
3
- import { union, subsequence, some, validate, guard, context, creator, surround, open, lazy, fmap, bind } from '../../combinator';
3
+ import { union, subsequence, some, validate, guard, context, precedence, creator, recursion, surround, open, lazy, bind } from '../../combinator';
4
4
  import { inline } from '../inline';
5
+ import { optimize } from './link';
5
6
  import { str, stropt } from '../source';
6
- import { regBlankStart, trimBlankStart, trimNodeEnd, stringify } from '../util';
7
+ import { regBlankStart, startLoose, trimNode, stringify } from '../util';
7
8
  import { html, defrag } from 'typed-dom/dom';
8
9
 
9
- export const reference: ReferenceParser = lazy(() => creator(validate('[[', fmap(surround(
10
+ export const reference: ReferenceParser = lazy(() => validate('[[', creator(recursion(precedence(6, surround(
10
11
  '[[',
11
12
  guard(context => context.syntax?.inline?.reference ?? true,
13
+ startLoose(
12
14
  context({ syntax: { inline: {
13
15
  annotation: false,
14
16
  reference: false,
@@ -21,11 +23,13 @@ export const reference: ReferenceParser = lazy(() => creator(validate('[[', fmap
21
23
  }}, delimiters: undefined },
22
24
  subsequence([
23
25
  abbr,
24
- open(stropt(/^(?=\^)/), some(inline, ']', /^\\?\n/)),
25
- trimBlankStart(some(inline, ']', /^\\?\n/)),
26
- ]))),
27
- ']]'),
28
- ns => [html('sup', attributes(ns), [html('span', trimNodeEnd(defrag(ns)))])]))));
26
+ open(stropt(/^(?=\^)/), some(inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])),
27
+ some(inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]]),
28
+ ])), ']')),
29
+ ']]',
30
+ false,
31
+ ([, ns], rest) => [[html('sup', attributes(ns), [html('span', trimNode(defrag(ns)))])], rest],
32
+ ([, ns, rest], next) => next[0] === ']' ? undefined : optimize('[[', ns, rest)))))));
29
33
 
30
34
  const abbr: ReferenceParser.AbbrParser = creator(bind(surround(
31
35
  '^',
@@ -1,47 +1,49 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { RubyParser } from '../inline';
3
3
  import { eval, exec } from '../../combinator/data/parser';
4
- import { sequence, validate, verify, focus, creator, surround, lazy, bind } from '../../combinator';
4
+ import { sequence, validate, verify, focus, creator, surround, lazy, fmap } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
6
  import { text as txt } from '../source';
7
7
  import { isStartTightNodes } from '../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
  import { unshift, push } from 'spica/array';
10
10
 
11
- export const ruby: RubyParser = lazy(() => creator(validate('[', bind(verify(
11
+ export const ruby: RubyParser = lazy(() => validate('[', creator(fmap(verify(
12
12
  sequence([
13
- surround('[', focus(/^(?:\\[^\n]|[^\\\[\]\n])+(?=]\()/, text), ']'),
14
- surround('(', focus(/^(?:\\[^\n]|[^\\\(\)\n])+(?=\))/, text), ')'),
13
+ surround('[', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=]\()/, text), ']'),
14
+ surround('(', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=\))/, text), ')'),
15
15
  ]),
16
16
  ([texts]) => isStartTightNodes(texts)),
17
- ([texts, rubies], rest) => {
18
- const tail = typeof texts[texts.length - 1] === 'object'
19
- ? [texts.pop()!]
20
- : [];
21
- tail.length === 0 && texts[texts.length - 1] === '' && texts.pop();
17
+ ([texts, rubies]) => {
18
+ texts[texts.length - 1] === '' && texts.pop();
22
19
  switch (true) {
23
20
  case rubies.length <= texts.length:
24
- return [[html('ruby', attributes(texts, rubies), defrag(push(texts
25
- .reduce((acc, _, i) =>
26
- push(acc, unshift([texts[i]],
27
- i < rubies.length && rubies[i]
28
- ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
29
- : [html('rt')]))
30
- , []), tail)))], rest];
21
+ return [
22
+ html('ruby', attributes(texts, rubies), defrag(texts
23
+ .reduce((acc, _, i) =>
24
+ push(acc, unshift([texts[i]],
25
+ i < rubies.length && rubies[i]
26
+ ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
27
+ : [html('rt')]))
28
+ , []))),
29
+ ];
31
30
  case texts.length === 1 && [...texts[0]].length >= rubies.length:
32
- return [[html('ruby', attributes(texts, rubies), defrag(push([...texts[0]]
33
- .reduce((acc, _, i, texts) =>
34
- push(acc, unshift([texts[i]],
35
- i < rubies.length && rubies[i]
36
- ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
37
- : [html('rt')]))
38
- , []), tail)))], rest];
31
+ return [
32
+ html('ruby', attributes(texts, rubies), defrag([...texts[0]]
33
+ .reduce((acc, _, i, texts) =>
34
+ push(acc, unshift([texts[i]],
35
+ i < rubies.length && rubies[i]
36
+ ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
37
+ : [html('rt')]))
38
+ , []))),
39
+ ];
39
40
  default:
40
41
  assert(rubies.length > 0);
41
- return [[html('ruby', attributes(texts, rubies), defrag(push(unshift(
42
- [texts.join(' ')],
43
- [html('rp', '('), html('rt', rubies.join(' ').trim()), html('rp', ')')]), tail)))
44
- ], rest];
42
+ return [
43
+ html('ruby', attributes(texts, rubies), defrag(unshift(
44
+ [texts.join(' ')],
45
+ [html('rp', '('), html('rt', rubies.join(' ').trim()), html('rp', ')')]))),
46
+ ];
45
47
  }
46
48
  }))));
47
49
 
@@ -1,5 +1,5 @@
1
1
  import { StrongParser } from '../inline';
2
- import { union, some, creator, surround, open, lazy } from '../../combinator';
2
+ import { union, some, precedence, creator, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { emstrong } from './emstrong';
5
5
  import { str } from '../source';
@@ -7,7 +7,7 @@ import { startTight, blankWith } from '../util';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
- export const strong: StrongParser = lazy(() => creator(surround(
10
+ export const strong: StrongParser = lazy(() => creator(precedence(1, surround(
11
11
  str('**'),
12
12
  startTight(some(union([
13
13
  some(inline, blankWith('**')),
@@ -18,4 +18,4 @@ export const strong: StrongParser = lazy(() => creator(surround(
18
18
  ])), '*'),
19
19
  str('**'), false,
20
20
  ([, bs], rest) => [[html('strong', defrag(bs))], rest],
21
- ([as, bs], rest) => [unshift(as, bs), rest])));
21
+ ([as, bs], rest) => [unshift(as, bs), rest]))));
@@ -1,17 +1,17 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { TemplateParser } from '../inline';
3
- import { union, some, rewrite, creator, surround, lazy } from '../../combinator';
3
+ import { union, some, rewrite, precedence, creator, surround, lazy } from '../../combinator';
4
4
  import { escsource, str } from '../source';
5
5
  import { html } from 'typed-dom/dom';
6
6
  import { unshift } from 'spica/array';
7
7
 
8
- export const template: TemplateParser = lazy(() => creator(rewrite(
8
+ export const template: TemplateParser = lazy(() => creator(precedence(2, rewrite(
9
9
  surround('{{', some(union([bracket, escsource]), '}'), '}}', true),
10
- source => [[html('span', { class: 'template' }, source.replace(/\x1B/g, ''))], ''])));
10
+ source => [[html('span', { class: 'template' }, source.replace(/\x1B/g, ''))], '']))));
11
11
 
12
12
  const bracket: TemplateParser.BracketParser = lazy(() => union([
13
13
  surround(str('('), some(union([bracket, escsource]), ')'), str(')'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
14
14
  surround(str('['), some(union([bracket, escsource]), ']'), str(']'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
15
15
  surround(str('{'), some(union([bracket, escsource]), '}'), str('}'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
16
- surround(str('"'), some(escsource, /^"|^\\?\n/), str('"'), true),
16
+ surround(str('"'), precedence(8, some(escsource, /^"|^\\?\n/)), str('"'), true),
17
17
  ]));
@@ -142,27 +142,32 @@ describe('Unit: parser/inline', () => {
142
142
  assert.deepStrictEqual(inspect(parser('${{{a}}}')), [['$', '<span class="template">{{{a}}}</span>'], '']);
143
143
  assert.deepStrictEqual(inspect(parser('Di$ney Micro$oft')), [['Di', '$', 'ney', ' ', 'Micro', '$', 'oft'], '']);
144
144
  assert.deepStrictEqual(inspect(parser('Di$ney, Micro$oft')), [['Di', '$', 'ney', ',', ' ', 'Micro', '$', 'oft'], '']);
145
- assert.deepStrictEqual(inspect(parser('(((a))')), [['(', '<sup class="annotation"><span>a</span></sup>'], '']);
146
- assert.deepStrictEqual(inspect(parser('((((a))')), [['(', '(', '<sup class="annotation"><span>a</span></sup>'], '']);
145
+ assert.deepStrictEqual(inspect(parser('(((a))')), [['', '(', '<sup class="annotation"><span>a</span></sup>'], '']);
146
+ assert.deepStrictEqual(inspect(parser('((((a))')), [['', '((', '<sup class="annotation"><span>a</span></sup>'], '']);
147
147
  assert.deepStrictEqual(inspect(parser('((((a))))')), [['<sup class="annotation"><span><span class="paren">((a))</span></span></sup>'], '']);
148
+ assert.deepStrictEqual(inspect(parser('((<bdi>))')), [['<sup class="annotation"><span><span class="invalid">&lt;bdi&gt;</span></span></sup>'], '']);
149
+ assert.deepStrictEqual(inspect(parser('((${))}$')), [['', '((', '<span class="math" translate="no" data-src="${))}$">${))}$</span>'], '']);
148
150
  assert.deepStrictEqual(inspect(parser('"((""))')), [['"', '<sup class="annotation"><span>""</span></sup>'], '']);
149
- assert.deepStrictEqual(inspect(parser('[[[a]]')), [['[', '<sup class="reference"><span>a</span></sup>'], '']);
150
- assert.deepStrictEqual(inspect(parser('[[[[a]]')), [['[', '[', '<sup class="reference"><span>a</span></sup>'], '']);
151
+ assert.deepStrictEqual(inspect(parser('[[[a]]')), [['', '[', '<sup class="reference"><span>a</span></sup>'], '']);
152
+ assert.deepStrictEqual(inspect(parser('[[[[a]]')), [['', '[[', '<sup class="reference"><span>a</span></sup>'], '']);
151
153
  assert.deepStrictEqual(inspect(parser('[[[[a]]]]')), [['<sup class="reference"><span>[[a]]</span></sup>'], '']);
152
154
  assert.deepStrictEqual(inspect(parser('[[[$-1]]]')), [['<sup class="reference"><span><a class="label" data-label="$-1">$-1</a></span></sup>'], '']);
153
155
  assert.deepStrictEqual(inspect(parser('[[[]{a}]]')), [['<sup class="reference"><span><a href="a">a</a></span></sup>'], '']);
154
156
  assert.deepStrictEqual(inspect(parser('[[[a]{b}]]')), [['<sup class="reference"><span><a href="b">a</a></span></sup>'], '']);
155
157
  assert.deepStrictEqual(inspect(parser('[(([a]{#}))]{#}')), [['<a href="#"><span class="paren">(<span class="paren">([a]{#})</span>)</span></a>'], '']);
158
+ assert.deepStrictEqual(inspect(parser('[[<bdi>]]')), [['<sup class="reference"><span><span class="invalid">&lt;bdi&gt;</span></span></sup>'], '']);
159
+ assert.deepStrictEqual(inspect(parser('[[${]]}$')), [['', '[[', '<span class="math" translate="no" data-src="${]]}$">${]]}$</span>'], '']);
156
160
  assert.deepStrictEqual(inspect(parser('"[[""]]')), [['"', '<sup class="reference"><span>""</span></sup>'], '']);
161
+ assert.deepStrictEqual(inspect(parser('[[a](b)]{c}')), [['<a href="c"><ruby>a<rp>(</rp><rt>b</rt><rp>)</rp></ruby></a>'], '']);
157
162
  assert.deepStrictEqual(inspect(parser('<http://host>')), [['<', '<a href="http://host" target="_blank">http://host</a>', '>'], '']);
158
- assert.deepStrictEqual(inspect(parser('[~http://host')), [['[', '~', '<a href="http://host" target="_blank">http://host</a>'], '']);
159
- assert.deepStrictEqual(inspect(parser('[~a@b')), [['[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
163
+ assert.deepStrictEqual(inspect(parser('[~http://host')), [['', '[', '~', '<a href="http://host" target="_blank">http://host</a>'], '']);
164
+ assert.deepStrictEqual(inspect(parser('[~a@b')), [['', '[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
160
165
  assert.deepStrictEqual(inspect(parser('[~~a~~]')), [['[', '<del>a</del>', ']'], '']);
161
166
  assert.deepStrictEqual(inspect(parser('[^http://host')), [['[^', '<a href="http://host" target="_blank">http://host</a>'], '']);
162
167
  assert.deepStrictEqual(inspect(parser('[^a@b')), [['[^', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
163
168
  assert.deepStrictEqual(inspect(parser('[#a*b\nc*]')), [['[', '<a href="/hashtags/a" class="hashtag">#a</a>', '<em>b<br>c</em>', ']'], '']);
164
169
  assert.deepStrictEqual(inspect(parser('[*a\nb*]{/}')), [['[', '<em>a<br>b</em>', ']', '<a href="/">/</a>'], '']);
165
- assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['[', '[', '<em>a<br>b</em>', ']', ']'], '']);
170
+ assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['', '[[', '<em>a<br>b</em>', ']', ']'], '']);
166
171
  assert.deepStrictEqual(inspect(parser('"[% *"*"*')), [['"', '[%', ' ', '*', '"', '*', '"', '*'], '']);
167
172
  assert.deepStrictEqual(inspect(parser('"[% "*"* %]')), [['"', '[%', ' ', '"', '*', '"', '*', ' ', '%', ']'], '']);
168
173
  });
@@ -228,7 +233,7 @@ describe('Unit: parser/inline', () => {
228
233
  assert.deepStrictEqual(inspect(parser('*a*#b')), [['<em>a</em>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
229
234
  assert.deepStrictEqual(inspect(parser('((a))#b')), [['<sup class="annotation"><span>a</span></sup>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
230
235
  assert.deepStrictEqual(inspect(parser('[[a]]#b')), [['<sup class="reference"><span>a</span></sup>', '<a href="/hashtags/b" class="hashtag">#b</a>'], '']);
231
- assert.deepStrictEqual(inspect(parser('[#a')), [['[', '<a href="/hashtags/a" class="hashtag">#a</a>'], '']);
236
+ assert.deepStrictEqual(inspect(parser('[#a')), [['', '[', '<a href="/hashtags/a" class="hashtag">#a</a>'], '']);
232
237
  assert.deepStrictEqual(inspect(parser('|#a')), [['|', '<a href="/hashtags/a" class="hashtag">#a</a>'], '']);
233
238
  assert.deepStrictEqual(inspect(parser(' #a')), [[' ', '<a href="/hashtags/a" class="hashtag">#a</a>'], '']);
234
239
  });
@@ -34,6 +34,7 @@ export import MathParser = InlineParser.MathParser;
34
34
  export import ExtensionParser = InlineParser.ExtensionParser;
35
35
  export import RubyParser = InlineParser.RubyParser;
36
36
  export import LinkParser = InlineParser.LinkParser;
37
+ export import TextLinkParser = InlineParser.TextLinkParser;
37
38
  export import HTMLParser = InlineParser.HTMLParser;
38
39
  export import InsertionParser = InlineParser.InsertionParser;
39
40
  export import DeletionParser = InlineParser.DeletionParser;