securemark 0.234.1 → 0.235.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 (36) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/securemark.js +128 -136
  3. package/markdown.d.ts +4 -16
  4. package/package-lock.json +46 -43
  5. package/package.json +1 -1
  6. package/src/combinator/control/constraint/block.ts +0 -2
  7. package/src/combinator/control/constraint/contract.ts +1 -1
  8. package/src/combinator/control/manipulation/context.ts +7 -0
  9. package/src/combinator/control/manipulation/match.ts +1 -1
  10. package/src/combinator/control/manipulation/scope.ts +1 -1
  11. package/src/combinator/data/parser/inits.ts +1 -1
  12. package/src/combinator/data/parser/sequence.ts +1 -1
  13. package/src/combinator/data/parser/some.ts +40 -18
  14. package/src/combinator/data/parser.ts +4 -1
  15. package/src/parser/api/normalize.ts +7 -6
  16. package/src/parser/block/heading.test.ts +1 -1
  17. package/src/parser/block/paragraph.test.ts +2 -0
  18. package/src/parser/inline/emphasis.test.ts +7 -4
  19. package/src/parser/inline/emphasis.ts +7 -7
  20. package/src/parser/inline/emstrong.ts +19 -18
  21. package/src/parser/inline/extension/index.test.ts +19 -18
  22. package/src/parser/inline/extension/index.ts +3 -4
  23. package/src/parser/inline/extension/indexer.test.ts +1 -0
  24. package/src/parser/inline/extension/indexer.ts +1 -0
  25. package/src/parser/inline/html.ts +4 -8
  26. package/src/parser/inline/htmlentity.test.ts +1 -0
  27. package/src/parser/inline/htmlentity.ts +9 -12
  28. package/src/parser/inline/link.ts +3 -4
  29. package/src/parser/inline/mark.test.ts +6 -3
  30. package/src/parser/inline/mark.ts +4 -4
  31. package/src/parser/inline/media.ts +8 -5
  32. package/src/parser/inline/ruby.ts +3 -4
  33. package/src/parser/inline/strong.test.ts +6 -4
  34. package/src/parser/inline/strong.ts +7 -7
  35. package/src/parser/inline.test.ts +15 -5
  36. package/src/parser/util.ts +11 -42
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.234.1",
3
+ "version": "0.235.0",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -449,9 +449,9 @@
449
449
  },
450
450
  "dependencies": {
451
451
  "lru-cache": {
452
- "version": "7.7.1",
453
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.1.tgz",
454
- "integrity": "sha512-cRffBiTW8s73eH4aTXqBcTLU0xQnwGV3/imttRHGWCrbergmnK4D6JXQd8qin5z43HnDwRI+o7mVW0LEB+tpAw==",
452
+ "version": "7.7.2",
453
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.2.tgz",
454
+ "integrity": "sha512-WkdIOIF7HkfVHXxKLjhH6lyAxSFoSO5NZpZS9cH8Oe5rAI2ZDrVmIweDAZUHqIhl0zasQUprVVR8uv2yggYYvw==",
455
455
  "dev": true
456
456
  },
457
457
  "mkdirp": {
@@ -1760,10 +1760,13 @@
1760
1760
  "dev": true
1761
1761
  },
1762
1762
  "builtins": {
1763
- "version": "1.0.3",
1764
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
1765
- "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
1766
- "dev": true
1763
+ "version": "5.0.0",
1764
+ "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.0.tgz",
1765
+ "integrity": "sha512-aizhtbxgT1Udg0Fj6GssXshAVK+nxbtCV+1OtTrMNy67jffDFBY6CUBAkhO4owbleAx6fdbnWdpsmmcXydbzNw==",
1766
+ "dev": true,
1767
+ "requires": {
1768
+ "semver": "^7.0.0"
1769
+ }
1767
1770
  },
1768
1771
  "bytes": {
1769
1772
  "version": "3.1.2",
@@ -1798,9 +1801,9 @@
1798
1801
  },
1799
1802
  "dependencies": {
1800
1803
  "lru-cache": {
1801
- "version": "7.7.1",
1802
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.1.tgz",
1803
- "integrity": "sha512-cRffBiTW8s73eH4aTXqBcTLU0xQnwGV3/imttRHGWCrbergmnK4D6JXQd8qin5z43HnDwRI+o7mVW0LEB+tpAw==",
1804
+ "version": "7.7.2",
1805
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.2.tgz",
1806
+ "integrity": "sha512-WkdIOIF7HkfVHXxKLjhH6lyAxSFoSO5NZpZS9cH8Oe5rAI2ZDrVmIweDAZUHqIhl0zasQUprVVR8uv2yggYYvw==",
1804
1807
  "dev": true
1805
1808
  },
1806
1809
  "mkdirp": {
@@ -1924,9 +1927,9 @@
1924
1927
  "dev": true
1925
1928
  },
1926
1929
  "caniuse-lite": {
1927
- "version": "1.0.30001320",
1928
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz",
1929
- "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==",
1930
+ "version": "1.0.30001322",
1931
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz",
1932
+ "integrity": "sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew==",
1930
1933
  "dev": true
1931
1934
  },
1932
1935
  "chalk": {
@@ -3030,9 +3033,9 @@
3030
3033
  "dev": true
3031
3034
  },
3032
3035
  "electron-to-chromium": {
3033
- "version": "1.4.96",
3034
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.96.tgz",
3035
- "integrity": "sha512-DPNjvNGPabv6FcyjzLAN4C0psN/GgD9rSGvMTuv81SeXG/EX3mCz0wiw9N1tUEnfQXYCJi3H8M0oFPRziZh7rw==",
3036
+ "version": "1.4.100",
3037
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.100.tgz",
3038
+ "integrity": "sha512-pNrSE2naf8fizl6/Uxq8UbKb8hU9EiYW4OzCYswosXoLV5NTMOUVKECNzDaHiUubsPq/kAckOzZd7zd8S8CHVw==",
3036
3039
  "dev": true
3037
3040
  },
3038
3041
  "elliptic": {
@@ -4608,9 +4611,9 @@
4608
4611
  "dev": true
4609
4612
  },
4610
4613
  "gauge": {
4611
- "version": "4.0.3",
4612
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.3.tgz",
4613
- "integrity": "sha512-ICw1DhAwMtb22rYFwEHgJcx1JCwJGv3x6G0OQUq56Nge+H4Q8JEwr8iveS0XFlsUNSI67F5ffMGK25bK4Pmskw==",
4614
+ "version": "4.0.4",
4615
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
4616
+ "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
4614
4617
  "dev": true,
4615
4618
  "requires": {
4616
4619
  "aproba": "^1.0.3 || ^2.0.0",
@@ -7092,9 +7095,9 @@
7092
7095
  }
7093
7096
  },
7094
7097
  "make-fetch-happen": {
7095
- "version": "10.1.0",
7096
- "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.1.0.tgz",
7097
- "integrity": "sha512-HeP4QlkadP/Op+hE+Une1070kcyN85FshQObku3/rmzRh4zDcKXA19d2L3AQR6UoaX3uZmhSOpTLH15b1vOFvQ==",
7098
+ "version": "10.1.1",
7099
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.1.1.tgz",
7100
+ "integrity": "sha512-3/mCljDQNjmrP7kl0vhS5WVlV+TvSKoZaFhdiYV7MOijEnrhrjaVnqbp/EY/7S+fhUB2KpH7j8c1iRsIOs+kjw==",
7098
7101
  "dev": true,
7099
7102
  "requires": {
7100
7103
  "agentkeepalive": "^4.2.1",
@@ -7116,9 +7119,9 @@
7116
7119
  },
7117
7120
  "dependencies": {
7118
7121
  "lru-cache": {
7119
- "version": "7.7.1",
7120
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.1.tgz",
7121
- "integrity": "sha512-cRffBiTW8s73eH4aTXqBcTLU0xQnwGV3/imttRHGWCrbergmnK4D6JXQd8qin5z43HnDwRI+o7mVW0LEB+tpAw==",
7122
+ "version": "7.7.2",
7123
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.2.tgz",
7124
+ "integrity": "sha512-WkdIOIF7HkfVHXxKLjhH6lyAxSFoSO5NZpZS9cH8Oe5rAI2ZDrVmIweDAZUHqIhl0zasQUprVVR8uv2yggYYvw==",
7122
7125
  "dev": true
7123
7126
  }
7124
7127
  }
@@ -8128,14 +8131,14 @@
8128
8131
  "dev": true
8129
8132
  },
8130
8133
  "npm-package-arg": {
8131
- "version": "9.0.1",
8132
- "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.0.1.tgz",
8133
- "integrity": "sha512-Xs9wznfEAmZAR61qsYH3iN24V/qMYYkvAR5CRQNMvC6PjN2fHtO8y9XP/xdp5K+Icx+u1wMBMgWRPCmAEChSog==",
8134
+ "version": "9.0.2",
8135
+ "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.0.2.tgz",
8136
+ "integrity": "sha512-v/miORuX8cndiOheW8p2moNuPJ7QhcFh9WGlTorruG8hXSA23vMTEp5hTCmDxic0nD8KHhj/NQgFuySD3GYY3g==",
8134
8137
  "dev": true,
8135
8138
  "requires": {
8136
8139
  "hosted-git-info": "^5.0.0",
8137
8140
  "semver": "^7.3.5",
8138
- "validate-npm-package-name": "^3.0.0"
8141
+ "validate-npm-package-name": "^4.0.0"
8139
8142
  },
8140
8143
  "dependencies": {
8141
8144
  "hosted-git-info": {
@@ -8148,9 +8151,9 @@
8148
8151
  }
8149
8152
  },
8150
8153
  "lru-cache": {
8151
- "version": "7.7.1",
8152
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.1.tgz",
8153
- "integrity": "sha512-cRffBiTW8s73eH4aTXqBcTLU0xQnwGV3/imttRHGWCrbergmnK4D6JXQd8qin5z43HnDwRI+o7mVW0LEB+tpAw==",
8154
+ "version": "7.7.2",
8155
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.2.tgz",
8156
+ "integrity": "sha512-WkdIOIF7HkfVHXxKLjhH6lyAxSFoSO5NZpZS9cH8Oe5rAI2ZDrVmIweDAZUHqIhl0zasQUprVVR8uv2yggYYvw==",
8154
8157
  "dev": true
8155
8158
  }
8156
8159
  }
@@ -8918,9 +8921,9 @@
8918
8921
  "dev": true
8919
8922
  },
8920
8923
  "proc-log": {
8921
- "version": "2.0.0",
8922
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.0.tgz",
8923
- "integrity": "sha512-I/35MfCX2H8jBUhKN8JB8nmqvQo/nKdrBodBY7L3RhDSPPyvOHwLYNmPuhwuJq7a7C3vgFKWGQM+ecPStcvOHA==",
8924
+ "version": "2.0.1",
8925
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz",
8926
+ "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==",
8924
8927
  "dev": true
8925
8928
  },
8926
8929
  "process": {
@@ -9187,9 +9190,9 @@
9187
9190
  }
9188
9191
  },
9189
9192
  "lru-cache": {
9190
- "version": "7.7.1",
9191
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.1.tgz",
9192
- "integrity": "sha512-cRffBiTW8s73eH4aTXqBcTLU0xQnwGV3/imttRHGWCrbergmnK4D6JXQd8qin5z43HnDwRI+o7mVW0LEB+tpAw==",
9193
+ "version": "7.7.2",
9194
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.2.tgz",
9195
+ "integrity": "sha512-WkdIOIF7HkfVHXxKLjhH6lyAxSFoSO5NZpZS9cH8Oe5rAI2ZDrVmIweDAZUHqIhl0zasQUprVVR8uv2yggYYvw==",
9193
9196
  "dev": true
9194
9197
  },
9195
9198
  "normalize-package-data": {
@@ -11307,12 +11310,12 @@
11307
11310
  }
11308
11311
  },
11309
11312
  "validate-npm-package-name": {
11310
- "version": "3.0.0",
11311
- "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
11312
- "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
11313
+ "version": "4.0.0",
11314
+ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz",
11315
+ "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==",
11313
11316
  "dev": true,
11314
11317
  "requires": {
11315
- "builtins": "^1.0.3"
11318
+ "builtins": "^5.0.0"
11316
11319
  }
11317
11320
  },
11318
11321
  "value-or-function": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.234.1",
3
+ "version": "0.235.0",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -6,10 +6,8 @@ export function block<P extends Parser<unknown>>(parser: P, separation?: boolean
6
6
  export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
7
7
  assert(parser);
8
8
  return (source, context) => {
9
- assert(!context?.delimiters);
10
9
  if (source === '') return;
11
10
  const result = parser(source, context);
12
- assert(!context?.delimiters);
13
11
  if (!result) return;
14
12
  const rest = exec(result);
15
13
  if (separation && !isEmpty(firstline(rest))) return;
@@ -15,7 +15,7 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[], has
15
15
  if (typeof end === 'function') return validate(patterns, has, '', end);
16
16
  if (!isArray(patterns)) return validate([patterns], has, end!, parser!);
17
17
  assert(patterns.length > 0);
18
- assert(patterns.every(pattern => pattern instanceof RegExp ? !pattern.global && pattern.source.startsWith('^') : true));
18
+ assert(patterns.every(pattern => pattern instanceof RegExp ? !pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^') : true));
19
19
  assert(parser);
20
20
  const match: (source: string) => boolean = Function([
21
21
  '"use strict";',
@@ -17,6 +17,7 @@ export function reset<P extends Parser<unknown>>(context: Context<P>, parser: P)
17
17
  export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
18
18
  assert(Object.getPrototypeOf(base) === Object.prototype);
19
19
  assert(Object.freeze(base));
20
+ if (isEmpty(base)) return parser;
20
21
  return (source, context) =>
21
22
  parser(source, inherit(ObjectCreate(context), base));
22
23
  }
@@ -25,6 +26,7 @@ export function context<P extends Parser<unknown>>(context: Context<P>, parser:
25
26
  export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
26
27
  assert(Object.getPrototypeOf(base) === Object.prototype);
27
28
  assert(Object.freeze(base));
29
+ if (isEmpty(base)) return parser;
28
30
  const override = memoize<Ctx, Ctx>(context => inherit(ObjectCreate(context), base), new WeakMap());
29
31
  return (source, context) =>
30
32
  parser(source, override(context));
@@ -56,3 +58,8 @@ const inherit = template((prop, target, source) => {
56
58
  return target[prop] = source[prop];
57
59
  }
58
60
  });
61
+
62
+ function isEmpty(context: Ctx): boolean {
63
+ for (const _ in context) return false;
64
+ return true;
65
+ }
@@ -3,7 +3,7 @@ import { Parser, exec, check } from '../../data/parser';
3
3
 
4
4
  export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
5
5
  export function match<T>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<T>): Parser<T> {
6
- assert(!pattern.global && pattern.source.startsWith('^'));
6
+ assert(!pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^'));
7
7
  return (source, context) => {
8
8
  if (source === '') return;
9
9
  const param = source.match(pattern);
@@ -3,7 +3,7 @@ import { Parser, Context, eval, exec, check } from '../../data/parser';
3
3
 
4
4
  export function focus<P extends Parser<unknown>>(scope: string | RegExp, parser: P): P;
5
5
  export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
6
- assert(scope instanceof RegExp ? !scope.global && scope.source.startsWith('^') : scope);
6
+ assert(scope instanceof RegExp ? !scope.flags.match(/[gmy]/) && scope.source.startsWith('^') : scope);
7
7
  assert(parser);
8
8
  const match: (source: string) => string = typeof scope === 'string'
9
9
  ? source => source.slice(0, scope.length) === scope ? scope : ''
@@ -14,7 +14,7 @@ export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
14
14
  const result = parsers[i](rest, context);
15
15
  assert(check(rest, result));
16
16
  if (!result) break;
17
- assert(!context?.delimiters?.some(match => match(rest)));
17
+ assert(!context?.delimiters?.stack.some(sig => context.delimiters!.matchers[sig](rest)));
18
18
  nodes = nodes
19
19
  ? push(nodes, eval(result))
20
20
  : eval(result);
@@ -14,7 +14,7 @@ export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D
14
14
  const result = parsers[i](rest, context);
15
15
  assert(check(rest, result));
16
16
  if (!result) return;
17
- assert(!context?.delimiters?.some(match => match(rest)));
17
+ assert(!context?.delimiters?.stack.some(sig => context.delimiters!.matchers[sig](rest)));
18
18
  nodes = nodes
19
19
  ? push(nodes, eval(result))
20
20
  : eval(result);
@@ -1,33 +1,58 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { Parser, eval, exec, check } from '../parser';
3
+ import { memoize, reduce } from 'spica/memoize';
3
4
  import { push } from 'spica/array';
4
5
 
6
+ const signature = (pattern: string | RegExp | undefined): string => {
7
+ switch (typeof pattern) {
8
+ case 'undefined':
9
+ return signature('');
10
+ case 'string':
11
+ return `s:${pattern}`;
12
+ case 'object':
13
+ return `r/${pattern.source}/${pattern.flags}`;
14
+ }
15
+ };
16
+ const [matcher, delimiter] = [...Array(2)].map(() =>
17
+ memoize(
18
+ (pattern: string | RegExp | undefined): (source: string) => boolean => {
19
+ switch (typeof pattern) {
20
+ case 'undefined':
21
+ return () => false;
22
+ case 'string':
23
+ return source => source.slice(0, pattern.length) === pattern;
24
+ case 'object':
25
+ return reduce(source => pattern.test(source));
26
+ }
27
+ },
28
+ signature));
29
+
5
30
  export function some<P extends Parser<unknown>>(parser: P, until?: string | RegExp | number, deep?: string | RegExp, limit?: number): P;
6
31
  export function some<T>(parser: Parser<T>, until?: string | RegExp | number, deep?: string | RegExp, limit = -1): Parser<T> {
7
32
  assert(parser);
8
- assert(until instanceof RegExp ? !until.global && until.source.startsWith('^') : true);
33
+ assert(until instanceof RegExp ? !until.flags.match(/[gmy]/) && until.source.startsWith('^') : true);
34
+ assert(deep instanceof RegExp ? !deep.flags.match(/[gmy]/) && deep.source.startsWith('^') : true);
9
35
  if (typeof until === 'number') return some(parser, undefined, deep, until);
10
- const match: (source: string) => boolean = typeof until === 'string' && until !== undefined
11
- ? source => source.slice(0, until.length) === until
12
- : source => !!until && until.test(source);
13
- const delim: (source: string) => boolean = typeof deep === 'string' && deep !== undefined
14
- ? source => source.slice(0, deep.length) === deep
15
- : source => !!deep && deep.test(source);
16
- let memory = '';
36
+ const match = matcher(until);
37
+ const delim = delimiter(deep);
38
+ const sig = signature(deep);
17
39
  return (source, context) => {
18
- if (source === memory) return;
40
+ if (source === '') return;
19
41
  let rest = source;
20
42
  let nodes: T[] | undefined;
21
43
  if (context && deep) {
22
- context.delimiters
23
- ? context.delimiters.push(delim)
24
- : context.delimiters = [delim];
25
- assert(context.delimiters.length <= 3);
44
+ // bracket > annotation > bracket > reference > bracket > link > media | bracket
45
+ // bracket > annotation > bracket > reference > bracket > index > bracket
46
+ context.delimiters ??= { stack: [], matchers: {} };
47
+ context.delimiters.stack.push(sig);
48
+ context.delimiters.matchers[sig] ??= delim;
49
+ assert(context.delimiters.matchers[sig] === delim);
26
50
  }
51
+ const { stack, matchers } = context.delimiters ?? {};
27
52
  while (true) {
28
53
  if (rest === '') break;
29
54
  if (match(rest)) break;
30
- if (context?.delimiters?.some(match => match(rest))) break;
55
+ if (stack?.some(sig => matchers![sig](rest))) break;
31
56
  const result = parser(rest, context);
32
57
  assert.doesNotThrow(() => limit < 0 && check(rest, result));
33
58
  if (!result) break;
@@ -38,11 +63,8 @@ export function some<T>(parser: Parser<T>, until?: string | RegExp | number, dee
38
63
  if (limit >= 0 && source.length - rest.length > limit) break;
39
64
  }
40
65
  if (context && deep) {
41
- context.delimiters?.length! > 1
42
- ? context.delimiters?.pop()
43
- : context.delimiters = undefined;
66
+ stack?.pop();
44
67
  }
45
- memory = limit < 0 && rest || memory;
46
68
  assert(rest.length <= source.length);
47
69
  return nodes && rest.length < source.length
48
70
  ? [nodes, rest]
@@ -2,7 +2,10 @@ export interface Ctx {
2
2
  readonly resources?: {
3
3
  budget: number;
4
4
  };
5
- delimiters?: ((source: string) => boolean)[];
5
+ delimiters?: {
6
+ readonly stack: string[];
7
+ readonly matchers: Record<string, (source: string) => boolean>;
8
+ };
6
9
  }
7
10
 
8
11
  export type Parser<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
@@ -1,6 +1,5 @@
1
1
  import { eval } from '../../combinator/data/parser';
2
- import { htmlentity } from '../inline/htmlentity';
3
- import { stringify } from '../util';
2
+ import { unsafehtmlentity } from '../inline/htmlentity';
4
3
 
5
4
  const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
6
5
  assert(UNICODE_REPLACEMENT_CHARACTER.trim());
@@ -25,9 +24,9 @@ function sanitize(source: string): string {
25
24
 
26
25
  // https://dev.w3.org/html5/html-author/charref
27
26
  // https://en.wikipedia.org/wiki/Whitespace_character
28
- const unreadableHTMLEntityNames = [
29
- //'Tab',
30
- //'NewLine',
27
+ export const invisibleHTMLEntityNames = [
28
+ 'Tab',
29
+ 'NewLine',
31
30
  'NonBreakingSpace',
32
31
  'nbsp',
33
32
  'shy',
@@ -59,8 +58,10 @@ const unreadableHTMLEntityNames = [
59
58
  'InvisibleComma',
60
59
  'ic',
61
60
  ] as const;
61
+ const unreadableHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames.slice(2);
62
62
  const unreadableEscapableCharacters = unreadableHTMLEntityNames
63
- .map(name => stringify(eval(htmlentity(`&${name};`, {}))!));
63
+ .map(name => eval(unsafehtmlentity(`&${name};`, {}))![0]);
64
+ assert(unreadableEscapableCharacters.length === unreadableHTMLEntityNames.length);
64
65
  assert(unreadableEscapableCharacters.every(c => c.length === 1));
65
66
  const unreadableEscapableCharacter = new RegExp(`[${
66
67
  [...new Set<string>(unreadableEscapableCharacters)].join('')
@@ -74,7 +74,7 @@ describe('Unit: parser/block/heading', () => {
74
74
  assert.deepStrictEqual(inspect(parser('# a [#!http://host]')), [['<h1 id="index:!http://host">a<span class="indexer" data-index="!http://host"></span></h1>'], '']);
75
75
  assert.deepStrictEqual(inspect(parser('# a [#a((b))]')), [['<h1 id="index:a((b))">a<span class="indexer" data-index="a((b))"></span></h1>'], '']);
76
76
  assert.deepStrictEqual(inspect(parser('# a [#a[[b]]]')), [['<h1 id="index:a[[b]]">a<span class="indexer" data-index="a[[b]]"></span></h1>'], '']);
77
- assert.deepStrictEqual(inspect(parser('# a [#b|#c]')), [['<h1 id="index:c">a<span class="indexer" data-index="c"></span></h1>'], '']);
77
+ assert.deepStrictEqual(inspect(parser('# a [#b |#c]')), [['<h1 id="index:c">a<span class="indexer" data-index="c"></span></h1>'], '']);
78
78
  assert.deepStrictEqual(inspect(parser('# a [#b] [#c]')), [['<h1 id="index:c">a [#b]<span class="indexer" data-index="c"></span></h1>'], '']);
79
79
  assert.deepStrictEqual(inspect(parser('# a [#b] \n')), [['<h1 id="index:b">a<span class="indexer" data-index="b"></span></h1>'], '']);
80
80
  assert.deepStrictEqual(inspect(parser('# a \\[#b]')), [['<h1 id="index:a_[#b]">a [#b]</h1>'], '']);
@@ -20,6 +20,8 @@ describe('Unit: parser/block/paragraph', () => {
20
20
  assert.deepStrictEqual(inspect(parser('a\\ \n')), [['<p>a</p>'], '']);
21
21
  assert.deepStrictEqual(inspect(parser('a\\\n')), [['<p>a</p>'], '']);
22
22
  assert.deepStrictEqual(inspect(parser('a\\\nb')), [['<p>a<span class="linebreak"> </span>b</p>'], '']);
23
+ assert.deepStrictEqual(inspect(parser('a&NewLine;b')), [['<p>a&amp;NewLine;b</p>'], '']);
24
+ assert.deepStrictEqual(inspect(parser('&Tab;&NewLine;')), [['<p>&amp;Tab;&amp;NewLine;</p>'], '']);
23
25
  assert.deepStrictEqual(inspect(parser('<wbr>')), [['<p>&lt;wbr&gt;</p>'], '']);
24
26
  assert.deepStrictEqual(inspect(parser('<wbr>\n')), [['<p>&lt;wbr&gt;</p>'], '']);
25
27
  assert.deepStrictEqual(inspect(parser('<wbr>\na')), [['<p>&lt;wbr&gt;<br>a</p>'], '']);
@@ -11,8 +11,8 @@ describe('Unit: parser/inline/emphasis', () => {
11
11
  assert.deepStrictEqual(inspect(parser('*a')), [['*', 'a'], '']);
12
12
  assert.deepStrictEqual(inspect(parser('*a *')), [['*', 'a', ' ', '*'], '']);
13
13
  assert.deepStrictEqual(inspect(parser('*a\n*')), [['*', 'a', '<br>', '*'], '']);
14
- assert.deepStrictEqual(inspect(parser('*a\\ *')), [['*', 'a', ' '], '*']);
15
- assert.deepStrictEqual(inspect(parser('*a\\\n*')), [['*', 'a', '<span class="linebreak"> </span>'], '*']);
14
+ assert.deepStrictEqual(inspect(parser('*a\\ *')), [['*', 'a', ' ', '*'], '']);
15
+ assert.deepStrictEqual(inspect(parser('*a\\\n*')), [['*', 'a', '<span class="linebreak"> </span>', '*'], '']);
16
16
  assert.deepStrictEqual(inspect(parser('*a**b')), [['*', 'a', '**', 'b'], '']);
17
17
  assert.deepStrictEqual(inspect(parser('*a**b*')), [['*', 'a', '**', 'b', '*'], '']);
18
18
  assert.deepStrictEqual(inspect(parser('* *')), undefined);
@@ -37,10 +37,13 @@ describe('Unit: parser/inline/emphasis', () => {
37
37
  });
38
38
 
39
39
  it('nest', () => {
40
- assert.deepStrictEqual(inspect(parser('*a**b**c*')), [['<em>a<strong>b</strong>c</em>'], '']);
41
- assert.deepStrictEqual(inspect(parser('*a**b**c*d')), [['<em>a<strong>b</strong>c</em>'], 'd']);
42
40
  assert.deepStrictEqual(inspect(parser('*a *b**')), [['<em>a <em>b</em></em>'], '']);
43
41
  assert.deepStrictEqual(inspect(parser('*a **b***')), [['<em>a <strong>b</strong></em>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('*a\\ *b**')), [['<em>a <em>b</em></em>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('*a&Tab;*b**')), [['<em>a\t<em>b</em></em>'], '']);
44
+ assert.deepStrictEqual(inspect(parser('*a<wbr>*b**')), [['<em>a<wbr><em>b</em></em>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('*a**b**c*')), [['<em>a<strong>b</strong>c</em>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('*a**b**c*d')), [['<em>a<strong>b</strong>c</em>'], 'd']);
44
47
  assert.deepStrictEqual(inspect(parser('*`a`*')), [['<em><code data-src="`a`">a</code></em>'], '']);
45
48
  assert.deepStrictEqual(inspect(parser('*<small>*')), [['<em>&lt;small&gt;</em>'], '']);
46
49
  assert.deepStrictEqual(inspect(parser('*(*a*)*')), [['<em><span class="paren">(<em>a</em>)</span></em>'], '']);
@@ -1,19 +1,19 @@
1
1
  import { EmphasisParser } from '../inline';
2
- import { union, sequence, some, creator, surround, close, lazy } from '../../combinator';
2
+ import { union, some, creator, surround, open, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { strong } from './strong';
5
5
  import { str } from '../source';
6
- import { startTight, isEndTightNodes } from '../util';
6
+ import { startTight, isEndTightNodes, delimiter } from '../util';
7
7
  import { html, defrag } from 'typed-dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
- export const emphasis: EmphasisParser = lazy(() => creator(surround(close(
11
- str('*'), /^(?!\*)/),
10
+ export const emphasis: EmphasisParser = lazy(() => creator(surround(
11
+ str('*'),
12
12
  startTight(some(union([
13
13
  strong,
14
- some(inline, /^\s*\*/),
15
- sequence([some(inline, '*'), inline]),
16
- ]))),
14
+ some(inline, delimiter(/\*/)),
15
+ open(some(inline, '*'), inline),
16
+ ])), '*'),
17
17
  str('*'), false,
18
18
  ([as, bs, cs], rest) =>
19
19
  isEndTightNodes(bs)
@@ -1,42 +1,37 @@
1
1
  import { MarkdownParser } from '../../../markdown';
2
2
  import { EmStrongParser, EmphasisParser, StrongParser } from '../inline';
3
3
  import { Result, IntermediateParser } from '../../combinator/data/parser';
4
- import { union, sequence, some, creator, surround, lazy, bind } from '../../combinator';
4
+ import { union, some, creator, surround, open, lazy, bind } from '../../combinator';
5
5
  import { inline } from '../inline';
6
6
  import { strong } from './strong';
7
7
  import { str } from '../source';
8
- import { startTight, isEndTightNodes } from '../util';
8
+ import { startTight, isEndTightNodes, delimiter } from '../util';
9
9
  import { html, defrag } from 'typed-dom';
10
10
  import { unshift } from 'spica/array';
11
11
 
12
12
  const substrong: IntermediateParser<StrongParser> = lazy(() => some(union([
13
- some(inline, /^\s*\*\*/),
14
- sequence([some(inline, '*'), inline]),
13
+ some(inline, delimiter(/\*\*/)),
14
+ open(some(inline, '*'), inline),
15
15
  ])));
16
16
  const subemphasis: IntermediateParser<EmphasisParser> = lazy(() => some(union([
17
17
  strong,
18
- some(inline, /^\s*\*/),
19
- sequence([some(inline, '*'), inline]),
18
+ some(inline, delimiter(/\*/)),
19
+ open(some(inline, '*'), inline),
20
20
  ])));
21
21
 
22
22
  export const emstrong: EmStrongParser = lazy(() => creator(surround(
23
23
  str('***'),
24
24
  startTight(some(union([
25
- some(inline, /^\s*\*/),
26
- sequence([some(inline, '*'), inline]),
25
+ some(inline, delimiter(/\*/)),
26
+ open(some(inline, '*'), inline),
27
27
  ]))),
28
28
  str(/^\*{1,3}/), false,
29
29
  ([as, bs, cs], rest, context): Result<HTMLElement | string, MarkdownParser.Context> => {
30
+ assert(cs.length === 1);
30
31
  if (!isEndTightNodes(bs)) return [unshift(as, bs), cs[0] + rest];
31
32
  switch (cs[0]) {
32
- case '*':
33
- return bind<StrongParser>(
34
- substrong,
35
- (ds, rest) =>
36
- rest.slice(0, 2) === '**' && isEndTightNodes(ds)
37
- ? [[html('strong', unshift([html('em', defrag(bs))], defrag(ds)))], rest.slice(2)]
38
- : [unshift(['**', html('em', defrag(bs))], ds), rest])
39
- (rest, context) ?? [['**', html('em', defrag(bs))], rest];
33
+ case '***':
34
+ return [[html('em', [html('strong', defrag(bs))])], rest];
40
35
  case '**':
41
36
  return bind<EmphasisParser>(
42
37
  subemphasis,
@@ -45,8 +40,14 @@ export const emstrong: EmStrongParser = lazy(() => creator(surround(
45
40
  ? [[html('em', unshift([html('strong', defrag(bs))], defrag(ds)))], rest.slice(1)]
46
41
  : [unshift(['*', html('strong', defrag(bs))], ds), rest])
47
42
  (rest, context) ?? [['*', html('strong', defrag(bs))], rest];
48
- case '***':
49
- return [[html('em', [html('strong', defrag(bs))])], rest];
43
+ case '*':
44
+ return bind<StrongParser>(
45
+ substrong,
46
+ (ds, rest) =>
47
+ rest.slice(0, 2) === '**' && isEndTightNodes(ds)
48
+ ? [[html('strong', unshift([html('em', defrag(bs))], defrag(ds)))], rest.slice(2)]
49
+ : [unshift(['**', html('em', defrag(bs))], ds), rest])
50
+ (rest, context) ?? [['**', html('em', defrag(bs))], rest];
50
51
  }
51
52
  assert(false);
52
53
  },