securemark 0.234.2 → 0.235.1

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 (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/securemark.js +173 -158
  3. package/package-lock.json +40 -37
  4. package/package.json +1 -1
  5. package/src/combinator/control/constraint/block.ts +0 -2
  6. package/src/combinator/control/constraint/contract.ts +1 -1
  7. package/src/combinator/control/manipulation/context.test.ts +4 -4
  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/resource.ts +6 -3
  11. package/src/combinator/control/manipulation/scope.ts +1 -1
  12. package/src/combinator/control/manipulation/surround.ts +3 -4
  13. package/src/combinator/data/parser/inits.ts +1 -1
  14. package/src/combinator/data/parser/sequence.ts +1 -1
  15. package/src/combinator/data/parser/some.ts +41 -21
  16. package/src/combinator/data/parser.ts +33 -7
  17. package/src/parser/api/bind.test.ts +3 -3
  18. package/src/parser/api/normalize.ts +7 -6
  19. package/src/parser/api/parse.test.ts +12 -5
  20. package/src/parser/block/blockquote.test.ts +1 -1
  21. package/src/parser/block/extension/aside.test.ts +1 -1
  22. package/src/parser/block/extension/example.test.ts +2 -2
  23. package/src/parser/block/extension/fig.test.ts +20 -20
  24. package/src/parser/block/extension/figure.test.ts +31 -28
  25. package/src/parser/block/extension/figure.ts +4 -2
  26. package/src/parser/block/extension/table.ts +6 -6
  27. package/src/parser/block/heading.test.ts +1 -1
  28. package/src/parser/block.ts +1 -2
  29. package/src/parser/inline/bracket.ts +3 -3
  30. package/src/parser/inline/emphasis.ts +7 -10
  31. package/src/parser/inline/emstrong.ts +7 -8
  32. package/src/parser/inline/extension/index.test.ts +19 -18
  33. package/src/parser/inline/extension/index.ts +3 -4
  34. package/src/parser/inline/extension/indexer.test.ts +1 -0
  35. package/src/parser/inline/extension/indexer.ts +1 -0
  36. package/src/parser/inline/html.ts +7 -11
  37. package/src/parser/inline/htmlentity.ts +8 -11
  38. package/src/parser/inline/link.ts +3 -4
  39. package/src/parser/inline/mark.ts +3 -6
  40. package/src/parser/inline/media.ts +8 -5
  41. package/src/parser/inline/ruby.ts +3 -4
  42. package/src/parser/inline/strong.test.ts +1 -1
  43. package/src/parser/inline/strong.ts +7 -10
  44. package/src/parser/inline.test.ts +0 -1
  45. package/src/parser/processor/figure.test.ts +28 -28
  46. package/src/parser/processor/figure.ts +1 -1
  47. package/src/parser/util.ts +14 -51
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.234.2",
3
+ "version": "0.235.1",
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.3",
453
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
454
+ "integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
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.3",
1805
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
1806
+ "integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
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.103",
3037
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz",
3038
+ "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==",
3036
3039
  "dev": true
3037
3040
  },
3038
3041
  "elliptic": {
@@ -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.3",
7123
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
7124
+ "integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
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.3",
8155
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
8156
+ "integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
8154
8157
  "dev": true
8155
8158
  }
8156
8159
  }
@@ -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.3",
9194
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
9195
+ "integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
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.2",
3
+ "version": "0.235.1",
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";',
@@ -13,7 +13,7 @@ describe('Unit: combinator/context', () => {
13
13
  (s, context) => [[context.resources?.budget ?? NaN], s.slice(1)]));
14
14
 
15
15
  it('root', () => {
16
- const base: Context = { resources: { budget: 3 } };
16
+ const base: Context = { resources: { budget: 3, recursion: 1 } };
17
17
  const ctx: Context = {};
18
18
  assert.deepStrictEqual(reset(base, parser)('123', ctx), [[3, 2, 1], '']);
19
19
  assert(base.resources?.budget === 3);
@@ -24,8 +24,8 @@ describe('Unit: combinator/context', () => {
24
24
  });
25
25
 
26
26
  it('node', () => {
27
- const base: Context = { resources: { budget: 3 } };
28
- const ctx: Context = { resources: { budget: 1 } };
27
+ const base: Context = { resources: { budget: 3, recursion: 1 } };
28
+ const ctx: Context = { resources: { budget: 1, recursion: 1 } };
29
29
  assert.deepStrictEqual(reset(base, parser)('1', ctx), [[1], '']);
30
30
  assert(base.resources?.budget === 3);
31
31
  assert(ctx.resources?.budget === 0);
@@ -41,7 +41,7 @@ describe('Unit: combinator/context', () => {
41
41
 
42
42
  it('', () => {
43
43
  const base: Context = { state: true };
44
- const ctx: Context = { resources: { budget: 3 } };
44
+ const ctx: Context = { resources: { budget: 3, recursion: 1 } };
45
45
  assert.deepStrictEqual(context(base, parser)('123', ctx), [[true, true, true], '']);
46
46
  assert(ctx.resources?.budget === 0);
47
47
  assert(ctx.state === undefined);
@@ -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);
@@ -6,10 +6,13 @@ export function creator(cost: number | Parser<unknown>, parser?: Parser<unknown>
6
6
  if (typeof cost === 'function') return creator(1, cost);
7
7
  assert(cost > 0);
8
8
  return (source, context) => {
9
- const { resources } = context;
10
- if (resources?.budget! <= 0) throw new Error('Too many creations.');
9
+ const { resources = { budget: 1, recursion: 1 } } = context;
10
+ if (resources.budget <= 0) throw new Error('Too many creations.');
11
+ if (resources.recursion <= 0) throw new Error('Too much recursion.');
12
+ --resources.recursion;
11
13
  const result = parser!(source, context);
12
- if (result && resources) {
14
+ ++resources.recursion;
15
+ if (result) {
13
16
  resources.budget -= cost;
14
17
  }
15
18
  return result;
@@ -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 : ''
@@ -1,7 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { Parser, Result, Ctx, Tree, Context, SubParsers, SubTree, IntermediateParser, eval, exec, check } from '../../data/parser';
3
3
  import { fmap } from '../monad/fmap';
4
- import { creator } from './resource';
5
4
  import { unshift, push } from 'spica/array';
6
5
 
7
6
  export function surround<P extends Parser<unknown>, S = string>(
@@ -69,14 +68,14 @@ export function surround<T>(
69
68
  function match(pattern: string | RegExp): (source: string, context: Ctx) => [never[], string] | undefined {
70
69
  switch (typeof pattern) {
71
70
  case 'string':
72
- return creator(source => source.slice(0, pattern.length) === pattern ? [[], source.slice(pattern.length)] : undefined);
71
+ return source => source.slice(0, pattern.length) === pattern ? [[], source.slice(pattern.length)] : undefined;
73
72
  case 'object':
74
- return creator(source => {
73
+ return source => {
75
74
  const m = source.match(pattern);
76
75
  return m
77
76
  ? [[], source.slice(m[0].length)]
78
77
  : undefined;
79
- });
78
+ };
80
79
  }
81
80
  }
82
81
 
@@ -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?.match(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?.match(rest));
18
18
  nodes = nodes
19
19
  ? push(nodes, eval(result))
20
20
  : eval(result);
@@ -1,33 +1,56 @@
1
1
  import { undefined } from 'spica/global';
2
- import { Parser, eval, exec, check } from '../parser';
2
+ import { Parser, Delimiters, 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 = memoize(
17
+ (pattern: string | RegExp | undefined): (source: string) => boolean => {
18
+ switch (typeof pattern) {
19
+ case 'undefined':
20
+ return () => false;
21
+ case 'string':
22
+ return source => source.slice(0, pattern.length) === pattern;
23
+ case 'object':
24
+ return reduce(source => pattern.test(source));
25
+ }
26
+ },
27
+ signature);
28
+
5
29
  export function some<P extends Parser<unknown>>(parser: P, until?: string | RegExp | number, deep?: string | RegExp, limit?: number): P;
6
30
  export function some<T>(parser: Parser<T>, until?: string | RegExp | number, deep?: string | RegExp, limit = -1): Parser<T> {
7
31
  assert(parser);
8
- assert(until instanceof RegExp ? !until.global && until.source.startsWith('^') : true);
32
+ assert(until instanceof RegExp ? !until.flags.match(/[gmy]/) && until.source.startsWith('^') : true);
33
+ assert(deep instanceof RegExp ? !deep.flags.match(/[gmy]/) && deep.source.startsWith('^') : true);
9
34
  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 = '';
35
+ const match = matcher(until);
36
+ const delimiter = {
37
+ signature: signature(deep),
38
+ matcher: matcher(deep),
39
+ } as const;
17
40
  return (source, context) => {
18
- if (source === memory) return;
41
+ if (source === '') return;
19
42
  let rest = source;
20
43
  let nodes: T[] | undefined;
21
- if (context && deep) {
22
- context.delimiters
23
- ? context.delimiters.push(delim)
24
- : context.delimiters = [delim];
25
- assert(context.delimiters.length <= 3);
44
+ if (deep && context) {
45
+ // bracket > annotation > bracket > reference > bracket > link > media | bracket
46
+ // bracket > annotation > bracket > reference > bracket > index > bracket
47
+ context.delimiters ??= new Delimiters();
48
+ context.delimiters.push(delimiter);
26
49
  }
27
50
  while (true) {
28
51
  if (rest === '') break;
29
52
  if (match(rest)) break;
30
- if (context?.delimiters?.some(match => match(rest))) break;
53
+ if (context.delimiters?.match(rest)) break;
31
54
  const result = parser(rest, context);
32
55
  assert.doesNotThrow(() => limit < 0 && check(rest, result));
33
56
  if (!result) break;
@@ -37,12 +60,9 @@ export function some<T>(parser: Parser<T>, until?: string | RegExp | number, dee
37
60
  rest = exec(result);
38
61
  if (limit >= 0 && source.length - rest.length > limit) break;
39
62
  }
40
- if (context && deep) {
41
- context.delimiters?.length! > 1
42
- ? context.delimiters?.pop()
43
- : context.delimiters = undefined;
63
+ if (deep && context.delimiters) {
64
+ context.delimiters.pop();
44
65
  }
45
- memory = limit < 0 && rest || memory;
46
66
  assert(rest.length <= source.length);
47
67
  return nodes && rest.length < source.length
48
68
  ? [nodes, rest]
@@ -1,16 +1,16 @@
1
- export interface Ctx {
2
- readonly resources?: {
3
- budget: number;
4
- };
5
- delimiters?: ((source: string) => boolean)[];
6
- }
7
-
8
1
  export type Parser<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
9
2
  = (source: string, context: C) => Result<T, C, D>;
10
3
  export type Result<T, C extends Ctx = Ctx, D extends Parser<unknown, C>[] = any>
11
4
  = readonly [T[], string, C, D]
12
5
  | readonly [T[], string]
13
6
  | undefined;
7
+ export interface Ctx {
8
+ readonly resources?: {
9
+ budget: number;
10
+ recursion: number;
11
+ };
12
+ delimiters?: Delimiters;
13
+ }
14
14
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
15
15
  export type SubParsers<P extends Parser<unknown>> = P extends Parser<unknown, any, infer D> ? D : never;
16
16
  export type Context<P extends Parser<unknown>> = P extends Parser<unknown, infer C> ? C : never;
@@ -19,6 +19,32 @@ export type IntermediateParser<P extends Parser<unknown>> = Parser<SubTree<P>, C
19
19
  type ExtractSubTree<D extends Parser<unknown>[]> = ExtractSubParser<D> extends infer T ? T extends Parser<infer U> ? U : never : never;
20
20
  type ExtractSubParser<D extends Parser<unknown>[]> = D extends (infer P)[] ? P extends Parser<unknown> ? P : never : never;
21
21
 
22
+ export class Delimiters {
23
+ private readonly stack: string[] = [];
24
+ private readonly matchers: Record<string, (source: string) => boolean> = {};
25
+ public push(delimiter: { readonly signature: string; readonly matcher: (source: string) => boolean; }): void {
26
+ const { signature, matcher } = delimiter;
27
+ this.stack.push(signature);
28
+ this.matchers[signature] ??= matcher;
29
+ assert(this.matchers[signature] === matcher);
30
+ }
31
+ public pop(): void {
32
+ assert(this.stack.length > 0);
33
+ this.stack.pop();
34
+ }
35
+ public match(source: string): boolean {
36
+ const { stack, matchers } = this;
37
+ const log = {};
38
+ for (let i = 0; i < stack.length; ++i) {
39
+ const sig = stack[i];
40
+ if (sig in log) continue;
41
+ if (matchers[sig](source)) return true;
42
+ log[sig] = false;
43
+ }
44
+ return false;
45
+ }
46
+ }
47
+
22
48
  export { eval_ as eval };
23
49
  function eval_<T>(result: NonNullable<Result<T>>, default_?: T[]): T[];
24
50
  function eval_<T>(result: Result<T>, default_: T[]): T[];
@@ -34,7 +34,7 @@ describe('Unit: parser/api/bind', () => {
34
34
  assert.deepStrictEqual(
35
35
  inspect(iter),
36
36
  [
37
- '<h1 class="error" translate="no">Error: Too large input over 1,000,000 bytes.</h1>',
37
+ '<h1 class="error">Error: Too large input over 1,000,000 bytes.</h1>',
38
38
  `<pre class="error" translate="no">${'\n'.repeat(997)}...</pre>`,
39
39
  ]);
40
40
  });
@@ -46,9 +46,9 @@ describe('Unit: parser/api/bind', () => {
46
46
  assert.deepStrictEqual(
47
47
  inspect(iter, 3),
48
48
  [
49
- '<h1 class="error" translate="no">Error: Too large segment over 100,000 bytes.</h1>',
49
+ '<h1 class="error">Error: Too large segment over 100,000 bytes.</h1>',
50
50
  `<pre class="error" translate="no">${'\n'.repeat(997)}...</pre>`,
51
- '<h1 class="error" translate="no">Error: Too large segment over 100,000 bytes.</h1>',
51
+ '<h1 class="error">Error: Too large segment over 100,000 bytes.</h1>',
52
52
  ]);
53
53
  });
54
54
 
@@ -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('')
@@ -118,7 +118,7 @@ describe('Unit: parser/api/parse', () => {
118
118
  '<p><a href="https://source/hashtags/a" target="_blank" class="hashtag">#a</a></p>',
119
119
  '<p><a href="https://domain/hashtags/a" target="_blank" class="hashtag">#domain/a</a></p>',
120
120
  '<p><a class="index" href="#index:a">a</a></p>',
121
- '<figure data-label="$-a" data-group="$" data-number="1" id="label:$-a"><div class="figcontent"><div class="math" translate="no">$$\n$$</div></div><span class="figindex">(1)</span><figcaption></figcaption></figure>',
121
+ '<figure data-label="$-a" data-group="$" data-number="1" id="label:$-a"><div class="figcontent"><div class="math" translate="no">$$\n$$</div></div><figcaption><span class="figindex">(1)</span></figcaption></figure>',
122
122
  '<p><a class="label" data-label="$-a" href="#label:$-a">(1)</a></p>',
123
123
  '<p><sup class="annotation" id="annotation:ref:1" title="a"><span hidden="">a</span><a href="#annotation:def:1">*1</a></sup></p>',
124
124
  '<p><a href="https://source/x/a" target="_blank">a</a></p>',
@@ -201,7 +201,7 @@ describe('Unit: parser/api/parse', () => {
201
201
  assert.deepStrictEqual(
202
202
  [...parse('$-a\n$$\n$$\n\n(($-a[[b]][[c*d*]]))', { footnotes }).children].map(el => el.outerHTML),
203
203
  [
204
- '<figure data-label="$-a" data-group="$" data-number="1" id="label:$-a"><div class="figcontent"><div class="math" translate="no">$$\n$$</div></div><span class="figindex">(1)</span><figcaption></figcaption></figure>',
204
+ '<figure data-label="$-a" data-group="$" data-number="1" id="label:$-a"><div class="figcontent"><div class="math" translate="no">$$\n$$</div></div><figcaption><span class="figindex">(1)</span></figcaption></figure>',
205
205
  '<p><sup class="annotation" id="annotation:ref:1" title="(1)[1][2]"><span hidden=""><a class="label" data-label="$-a" href="#label:$-a">(1)</a><sup class="reference" id="reference:ref:1" title="b"><span hidden="">b</span><a href="#reference:def:1">[1]</a></sup><sup class="reference" id="reference:ref:2" title="cd"><span hidden="">c<em>d</em></span><a href="#reference:def:2">[2]</a></sup></span><a href="#annotation:def:1">*1</a></sup></p>',
206
206
  ]);
207
207
  assert.deepStrictEqual(
@@ -218,10 +218,17 @@ describe('Unit: parser/api/parse', () => {
218
218
  ['<p>a<span class="linebreak"> </span>b</p>']);
219
219
  });
220
220
 
221
- it('reset', () => {
221
+ it('recursion', () => {
222
222
  assert.deepStrictEqual(
223
- [...parse(`"${'('.repeat(10000)}\n\n"`).children].slice(2).map(el => el.outerHTML),
224
- ['<p>"</p>']);
223
+ [...parse('('.repeat(199)).children].map(el => el.outerHTML),
224
+ [`<p>${'('.repeat(199)}</p>`]);
225
+ assert.deepStrictEqual(
226
+ [...parse('('.repeat(200) + '\n\na').children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
227
+ [
228
+ '<h1 id="error:rnd" class="error">Error: Too much recursion.</h1>',
229
+ `<pre class="error" translate="no">${'('.repeat(200)}\n</pre>`,
230
+ '<p>a</p>',
231
+ ]);
225
232
  });
226
233
 
227
234
  });
@@ -95,7 +95,7 @@ describe('Unit: parser/block/blockquote', () => {
95
95
  assert.deepStrictEqual(inspect(parser('!>\n> a')), [['<blockquote><section><p>a</p><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>'], '']);
96
96
  assert.deepStrictEqual(inspect(parser('!>> ## a\n> ## a')), [['<blockquote><blockquote><section><h2>a</h2><ol class="annotations"></ol><ol class="references"></ol></section></blockquote><section><h2>a</h2><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>'], '']);
97
97
  assert.deepStrictEqual(inspect(parser('!>> ~ a\n> ~ a')), [['<blockquote><blockquote><section><dl><dt>a</dt><dd></dd></dl><ol class="annotations"></ol><ol class="references"></ol></section></blockquote><section><dl><dt>a</dt><dd></dd></dl><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>'], '']);
98
- assert.deepStrictEqual(inspect(parser('!>> ~~~figure $fig-a\n>> > \n>>\n~~~\n> ~~~figure $fig-a\n> > \n>\n[#a]\n~~~')), [['<blockquote><blockquote><section><figure data-label="fig-a" data-group="fig" data-number="1"><div class="figcontent"><blockquote></blockquote></div><span class="figindex">Fig. 1. </span><figcaption></figcaption></figure><ol class="annotations"></ol><ol class="references"></ol></section></blockquote><section><figure data-label="fig-a" data-group="fig" data-number="1"><div class="figcontent"><blockquote></blockquote></div><span class="figindex">Fig. 1. </span><figcaption><a class="index">a</a></figcaption></figure><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>'], '']);
98
+ assert.deepStrictEqual(inspect(parser('!>> ~~~figure $fig-a\n>> > \n>>\n~~~\n> ~~~figure $fig-a\n> > \n>\n[#a]\n~~~')), [['<blockquote><blockquote><section><figure data-label="fig-a" data-group="fig" data-number="1"><div class="figcontent"><blockquote></blockquote></div><figcaption><span class="figindex">Fig. 1. </span></figcaption></figure><ol class="annotations"></ol><ol class="references"></ol></section></blockquote><section><figure data-label="fig-a" data-group="fig" data-number="1"><div class="figcontent"><blockquote></blockquote></div><figcaption><span class="figindex">Fig. 1. </span><a class="index">a</a></figcaption></figure><ol class="annotations"></ol><ol class="references"></ol></section></blockquote>'], '']);
99
99
  assert.deepStrictEqual(inspect(parser('!>> ((a))\n> ((a))')), [['<blockquote><blockquote><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup></p><ol class="annotations"><li>a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></blockquote><section><p><sup class="annotation disabled" title="a"><span hidden="">a</span><a>*1</a></sup></p><ol class="annotations"><li>a<sup><a>^1</a></sup></li></ol><ol class="references"></ol></section></blockquote>'], '']);
100
100
  });
101
101
 
@@ -16,7 +16,7 @@ describe('Unit: parser/block/extension/aside', () => {
16
16
  it('valid', () => {
17
17
  assert.deepStrictEqual(inspect(parser('~~~aside\n# 0\n~~~')), [['<aside id="index:0" class="aside"><h1>0</h1><ol class="annotations"></ol><ol class="references"></ol></aside>'], '']);
18
18
  assert.deepStrictEqual(inspect(parser('~~~aside\n## 0\n~~~')), [['<aside id="index:0" class="aside"><h2>0</h2><ol class="annotations"></ol><ol class="references"></ol></aside>'], '']);
19
- assert.deepStrictEqual(inspect(parser('~~~aside\n# 0\n\n$-0.0\n\n## 1\n\n$fig-a\n> \n~~~')), [['<aside id="index:0" class="aside"><h1>0</h1><figure data-label="$-0.0" data-group="$" hidden="" data-number="0.0"></figure><h2>1</h2><figure data-label="fig-a" data-group="fig" data-number="1.1"><div class="figcontent"><blockquote></blockquote></div><span class="figindex">Fig. 1.1. </span><figcaption></figcaption></figure><ol class="annotations"></ol><ol class="references"></ol></aside>'], '']);
19
+ assert.deepStrictEqual(inspect(parser('~~~aside\n# 0\n\n$-0.0\n\n## 1\n\n$fig-a\n> \n~~~')), [['<aside id="index:0" class="aside"><h1>0</h1><figure data-label="$-0.0" data-group="$" hidden="" data-number="0.0"></figure><h2>1</h2><figure data-label="fig-a" data-group="fig" data-number="1.1"><div class="figcontent"><blockquote></blockquote></div><figcaption><span class="figindex">Fig. 1.1. </span></figcaption></figure><ol class="annotations"></ol><ol class="references"></ol></aside>'], '']);
20
20
  });
21
21
 
22
22
  });