securemark 0.258.0 → 0.258.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.258.1
4
+
5
+ - Refactoring.
6
+
3
7
  ## 0.258.0
4
8
 
5
9
  - Introduce memoization.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.258.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.258.1 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
2
2
  (function webpackUniversalModuleDefinition(root, factory) {
3
3
  if(typeof exports === 'object' && typeof module === 'object')
4
4
  module.exports = factory(require("DOMPurify"), require("Prism"));
@@ -2543,9 +2543,9 @@ function focus(scope, parser) {
2543
2543
  const src = match(source);
2544
2544
  if (src === '') return;
2545
2545
  const memo = context.memo;
2546
- context.memo = global_1.undefined;
2546
+ memo && (memo.offset = source.length - src.length);
2547
2547
  const result = parser(src, context);
2548
- context.memo = memo;
2548
+ memo && (memo.offset = source.length + src.length);
2549
2549
  if (!result) return;
2550
2550
  return (0, parser_1.exec)(result).length < src.length ? [(0, parser_1.eval)(result), (0, parser_1.exec)(result) + source.slice(src.length)] : global_1.undefined;
2551
2551
  };
@@ -2562,9 +2562,9 @@ function rewrite(scope, parser) {
2562
2562
  context.memo = memo;
2563
2563
  if (!res1 || (0, parser_1.exec)(res1).length >= source.length) return;
2564
2564
  const src = source.slice(0, source.length - (0, parser_1.exec)(res1).length);
2565
- context.memo = global_1.undefined;
2565
+ memo && (memo.offset = source.length - src.length);
2566
2566
  const res2 = parser(src, context);
2567
- context.memo = memo;
2567
+ memo && (memo.offset = source.length + src.length);
2568
2568
  if (!res2) return;
2569
2569
  return (0, parser_1.exec)(res2).length < src.length ? [(0, parser_1.eval)(res2), (0, parser_1.exec)(res2) + (0, parser_1.exec)(res1)] : global_1.undefined;
2570
2570
  };
@@ -2851,10 +2851,8 @@ function syntax(syntax, precedence, cost, parser) {
2851
2851
 
2852
2852
  return (source, context) => {
2853
2853
  if (source === '') return;
2854
- const r = context.rule ?? 0;
2855
- context.rule = r | syntax;
2856
2854
  context.backtrackable ??= ~0;
2857
- context.state ??= 0;
2855
+ const state = context.state ??= 0;
2858
2856
  const p = context.precedence;
2859
2857
  context.precedence = precedence;
2860
2858
  const {
@@ -2867,7 +2865,7 @@ function syntax(syntax, precedence, cost, parser) {
2867
2865
  if (resources.recursion <= 0) throw new Error('Too much recursion');
2868
2866
  --resources.recursion;
2869
2867
  const pos = source.length;
2870
- const cache = context.memo?.get(pos, context.rule, syntax, context.state);
2868
+ const cache = syntax && context.memo?.get(pos, syntax, state);
2871
2869
  const result = cache ? [cache[0], source.slice(cache[1])] : parser(source, context);
2872
2870
  ++resources.recursion;
2873
2871
 
@@ -2877,9 +2875,9 @@ function syntax(syntax, precedence, cost, parser) {
2877
2875
  }
2878
2876
 
2879
2877
  if (syntax) {
2880
- if (r & context.backtrackable) {
2878
+ if (state & context.backtrackable) {
2881
2879
  context.memo ??= new memo_1.Memo();
2882
- cache ?? context.memo.set(pos, context.rule, syntax, context.state, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result).length);
2880
+ cache ?? context.memo.set(pos, syntax, state, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result).length);
2883
2881
  } else if (context.memo?.length >= pos) {
2884
2882
  context.memo.clear(pos);
2885
2883
  }
@@ -2887,7 +2885,6 @@ function syntax(syntax, precedence, cost, parser) {
2887
2885
  }
2888
2886
 
2889
2887
  context.precedence = p;
2890
- context.rule = r;
2891
2888
  return result;
2892
2889
  };
2893
2890
  }
@@ -3056,7 +3053,7 @@ Delimiters.matcher = (0, memoize_1.memoize)(pattern => {
3056
3053
  /***/ }),
3057
3054
 
3058
3055
  /***/ 1090:
3059
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
3056
+ /***/ ((__unused_webpack_module, exports) => {
3060
3057
 
3061
3058
  "use strict";
3062
3059
 
@@ -3066,28 +3063,33 @@ Object.defineProperty(exports, "__esModule", ({
3066
3063
  }));
3067
3064
  exports.Memo = void 0;
3068
3065
 
3069
- const array_1 = __webpack_require__(8112);
3070
-
3071
3066
  class Memo {
3072
3067
  constructor() {
3073
3068
  this.memory = [];
3069
+ this.offset = 0;
3074
3070
  }
3075
3071
 
3076
3072
  get length() {
3077
3073
  return this.memory.length;
3078
3074
  }
3079
3075
 
3080
- get(position, rule, syntax, state) {
3081
- return this.memory[position - 1]?.[`${rule}:${syntax}:${state}`];
3076
+ get(position, syntax, state) {
3077
+ //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
3078
+ return this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
3082
3079
  }
3083
3080
 
3084
- set(position, rule, syntax, state, nodes, offset) {
3085
- const record = this.memory[position - 1] ??= {};
3086
- record[`${rule}:${syntax}:${state}`] = [nodes, offset];
3081
+ set(position, syntax, state, nodes, offset) {
3082
+ const record = this.memory[position + this.offset - 1] ??= {};
3083
+ record[`${syntax}:${state}`] = [nodes.slice(), offset]; //console.log('set', position + this.offset, syntax, state);
3087
3084
  }
3088
3085
 
3089
3086
  clear(position) {
3090
- (0, array_1.splice)(this.memory, position, this.memory.length - position);
3087
+ const memory = this.memory;
3088
+
3089
+ for (let i = position + this.offset, len = memory.length; i < len; ++i) {
3090
+ memory.pop();
3091
+ } //console.log(position);
3092
+
3091
3093
  }
3092
3094
 
3093
3095
  }
@@ -3113,7 +3115,7 @@ const parser_1 = __webpack_require__(6728);
3113
3115
 
3114
3116
  const array_1 = __webpack_require__(8112);
3115
3117
 
3116
- function inits(parsers) {
3118
+ function inits(parsers, resume) {
3117
3119
  if (parsers.length === 1) return parsers[0];
3118
3120
  return (source, context) => {
3119
3121
  let rest = source;
@@ -3126,6 +3128,7 @@ function inits(parsers) {
3126
3128
  if (!result) break;
3127
3129
  nodes = nodes ? (0, array_1.push)(nodes, (0, parser_1.eval)(result)) : (0, parser_1.eval)(result);
3128
3130
  rest = (0, parser_1.exec)(result);
3131
+ if (resume?.((0, parser_1.eval)(result), (0, parser_1.exec)(result)) === false) break;
3129
3132
  }
3130
3133
 
3131
3134
  return nodes && rest.length < source.length ? [nodes, rest] : global_1.undefined;
@@ -3153,7 +3156,7 @@ const parser_1 = __webpack_require__(6728);
3153
3156
 
3154
3157
  const array_1 = __webpack_require__(8112);
3155
3158
 
3156
- function sequence(parsers) {
3159
+ function sequence(parsers, resume) {
3157
3160
  if (parsers.length === 1) return parsers[0];
3158
3161
  return (source, context) => {
3159
3162
  let rest = source;
@@ -3166,6 +3169,7 @@ function sequence(parsers) {
3166
3169
  if (!result) return;
3167
3170
  nodes = nodes ? (0, array_1.push)(nodes, (0, parser_1.eval)(result)) : (0, parser_1.eval)(result);
3168
3171
  rest = (0, parser_1.exec)(result);
3172
+ if (resume?.((0, parser_1.eval)(result), (0, parser_1.exec)(result)) === false) return;
3169
3173
  }
3170
3174
 
3171
3175
  return nodes && rest.length < source.length ? [nodes, rest] : global_1.undefined;
@@ -3251,8 +3255,8 @@ const union_1 = __webpack_require__(6366);
3251
3255
 
3252
3256
  const inits_1 = __webpack_require__(2491);
3253
3257
 
3254
- function subsequence(parsers) {
3255
- return (0, union_1.union)(parsers.map((_, i) => i + 1 < parsers.length ? (0, inits_1.inits)([parsers[i], subsequence(parsers.slice(i + 1))]) : parsers[i]));
3258
+ function subsequence(parsers, resume) {
3259
+ return (0, union_1.union)(parsers.map((_, i) => i + 1 < parsers.length ? (0, inits_1.inits)([parsers[i], subsequence(parsers.slice(i + 1), resume)], resume) : parsers[i]));
3256
3260
  }
3257
3261
 
3258
3262
  exports.subsequence = subsequence;
@@ -3274,8 +3278,8 @@ const union_1 = __webpack_require__(6366);
3274
3278
 
3275
3279
  const sequence_1 = __webpack_require__(2287);
3276
3280
 
3277
- function tails(parsers) {
3278
- return (0, union_1.union)(parsers.map((_, i) => (0, sequence_1.sequence)(parsers.slice(i))));
3281
+ function tails(parsers, resume) {
3282
+ return (0, union_1.union)(parsers.map((_, i) => (0, sequence_1.sequence)(parsers.slice(i), resume)));
3279
3283
  }
3280
3284
 
3281
3285
  exports.tails = tails;
@@ -5482,16 +5486,16 @@ Object.defineProperty(exports, "__esModule", ({
5482
5486
  value: true
5483
5487
  }));
5484
5488
  exports.backtrackable = void 0;
5485
- exports.backtrackable = 0 | 32
5486
- /* Rule.annotation */
5487
- | 4096
5488
- /* Rule.reference */
5489
- | 1024
5490
- /* Rule.index */
5491
- | 256
5492
- /* Rule.link */
5493
- | 64
5494
- /* Rule.media */
5489
+ exports.backtrackable = 0 | 64
5490
+ /* State.annotation */
5491
+ | 32
5492
+ /* State.reference */
5493
+ | 16
5494
+ /* State.index */
5495
+ | 4
5496
+ /* State.link */
5497
+ | 2
5498
+ /* State.media */
5495
5499
  ;
5496
5500
 
5497
5501
  /***/ }),
@@ -5678,7 +5682,7 @@ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('((
5678
5682
  delimiters: global_1.undefined
5679
5683
  }, (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')'))), '))', false, ([, ns], rest) => [[(0, dom_1.html)('sup', {
5680
5684
  class: 'annotation'
5681
- }, [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest], ([, ns, rest], next) => next[0] === ')' ? global_1.undefined : (0, link_1.optimize)('((', ns, rest)))));
5685
+ }, [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest], ([, ns, rest], next) => next[0] === ')' ? global_1.undefined : (0, link_1.optimize)('((', ns, rest, next)))));
5682
5686
 
5683
5687
  /***/ }),
5684
5688
 
@@ -6663,10 +6667,9 @@ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'
6663
6667
  /* State.media */
6664
6668
  | 1
6665
6669
  /* State.autolink */
6666
- , (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2]])), ']', true, global_1.undefined, ([, ns = [], rest], next) => next[0] === ']' ? global_1.undefined : optimize('[', ns, rest))]))), // 全体の失敗が確定した時も解析し予算を浪費している
6667
- (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))]), ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []])), ([content, params], rest, context) => {
6668
- if (params.length === 0) return;
6670
+ , (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2]])), ']', true, global_1.undefined, ([, ns = [], rest], next) => next[0] === ']' ? global_1.undefined : optimize('[', ns, rest, next))]))), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))], nodes => nodes[0][0] !== ''), ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []])), ([content, params], rest, context) => {
6669
6671
  if (content[0] === '') return [content, rest];
6672
+ if (params.length === 0) return;
6670
6673
  if (content.length !== 0 && (0, visibility_1.trimNode)(content).length === 0) return;
6671
6674
  if ((0, parser_1.eval)((0, combinator_1.some)(autolink_1.autolink)((0, util_1.stringify)(content), context))?.some(node => typeof node === 'object')) return;
6672
6675
  const INSECURE_URI = params.shift();
@@ -6760,12 +6763,15 @@ function decode(uri) {
6760
6763
  }
6761
6764
  }
6762
6765
 
6763
- function optimize(opener, ns, rest) {
6766
+ function optimize(opener, ns, rest, next) {
6767
+ if (next[+(next[0] === '\\')] === '\n') return;
6764
6768
  let count = 0;
6765
6769
 
6766
6770
  for (let i = 0; i < ns.length - 1; i += 2) {
6767
- if (ns[i] !== '' || ns[i + 1] !== opener[0]) break;
6768
- ++count;
6771
+ const fst = ns[i];
6772
+ const snd = ns[i + 1];
6773
+ if (fst !== '' || snd[0] !== opener[0]) break;
6774
+ count += snd.length;
6769
6775
  }
6770
6776
 
6771
6777
  return [['', opener[0].repeat(opener.length + count)], rest.slice(count)];
@@ -6990,7 +6996,7 @@ exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[['
6990
6996
  /* State.media */
6991
6997
  , (0, visibility_1.startLoose)((0, combinator_1.context)({
6992
6998
  delimiters: global_1.undefined
6993
- }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])])), ']'))), ']]', false, ([, ns], rest) => [[(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest], ([, ns, rest], next) => next[0] === ']' ? global_1.undefined : (0, link_1.optimize)('[[', ns, rest)))));
6999
+ }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])])), ']'))), ']]', false, ([, ns], rest) => [[(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest], ([, ns, rest], next) => next[0] === ']' ? global_1.undefined : (0, link_1.optimize)('[[', ns, rest, next)))));
6994
7000
  const abbr = (0, combinator_1.creator)((0, combinator_1.bind)((0, combinator_1.surround)('^', (0, combinator_1.union)([(0, source_1.str)(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]), /^\|?(?=]])|^\|[^\S\n]*/), ([source], rest) => [[(0, dom_1.html)('abbr', source)], rest.replace(visibility_1.regBlankStart, '')]));
6995
7001
 
6996
7002
  function attributes(ns) {
@@ -7191,7 +7197,7 @@ exports.template = (0, combinator_1.lazy)(() => (0, combinator_1.syntax)(0
7191
7197
  /* Rule.none */
7192
7198
  , 2, (0, combinator_1.surround)('{{', (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), '}'), '}}', true, ([, ns = []], rest) => [[(0, dom_1.html)('span', {
7193
7199
  class: 'template'
7194
- }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest], ([, ns = [], rest], next) => next[0] === '}' ? global_1.undefined : (0, link_1.optimize)('{{', ns, rest))));
7200
+ }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest], ([, ns = [], rest], next) => next[0] === '}' ? global_1.undefined : (0, link_1.optimize)('{{', ns, rest, next))));
7195
7201
  const bracket = (0, combinator_1.lazy)(() => (0, combinator_1.creator)((0, combinator_1.union)([(0, combinator_1.surround)((0, source_1.str)('('), (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), ')'), (0, source_1.str)(')'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('['), (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), ']'), (0, source_1.str)(']'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('{'), (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), '}'), (0, source_1.str)('}'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('"'), (0, combinator_1.precedence)(8, (0, combinator_1.some)(source_1.escsource, /^"|^\\?\n/)), (0, source_1.str)('"'), true)])));
7196
7202
 
7197
7203
  /***/ }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.258.0",
3
+ "version": "0.258.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",
@@ -14,10 +14,10 @@ export function focus<T>(scope: string | RegExp, parser: Parser<T>): Parser<T> {
14
14
  assert(source.startsWith(src));
15
15
  if (src === '') return;
16
16
  const memo = context.memo;
17
- context.memo = undefined;
17
+ memo && (memo.offset = source.length - src.length);
18
18
  const result = parser(src, context);
19
19
  assert(check(src, result));
20
- context.memo = memo;
20
+ memo && (memo.offset = source.length + src.length);
21
21
  if (!result) return;
22
22
  assert(exec(result).length < src.length);
23
23
  return exec(result).length < src.length
@@ -42,10 +42,10 @@ export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T>
42
42
  const src = source.slice(0, source.length - exec(res1).length);
43
43
  assert(src !== '');
44
44
  assert(source.startsWith(src));
45
- context.memo = undefined;
45
+ memo && (memo.offset = source.length - src.length);
46
46
  const res2 = parser(src, context);
47
47
  assert(check(src, res2));
48
- context.memo = memo;
48
+ memo && (memo.offset = source.length + src.length);
49
49
  if (!res2) return;
50
50
  assert(exec(res2) === '');
51
51
  return exec(res2).length < src.length
@@ -1,30 +1,34 @@
1
- import { splice } from 'spica/array';
2
-
3
1
  export class Memo {
4
2
  private memory: Record<string, readonly [any[], number]>[/* pos */] = [];
5
3
  public get length(): number {
6
4
  return this.memory.length;
7
5
  }
6
+ public offset = 0;
8
7
  public get(
9
8
  position: number,
10
- rule: number,
11
9
  syntax: number,
12
10
  state: number,
13
11
  ): readonly [any[], number] | undefined {
14
- return this.memory[position - 1]?.[`${rule}:${syntax}:${state}`];
12
+ //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
13
+ return this.memory[position + this.offset - 1]?.[`${syntax}:${state}`];
15
14
  }
16
15
  public set(
17
16
  position: number,
18
- rule: number,
19
17
  syntax: number,
20
18
  state: number,
21
19
  nodes: any[],
22
20
  offset: number,
23
21
  ): void {
24
- const record = this.memory[position - 1] ??= {};
25
- record[`${rule}:${syntax}:${state}`] = [nodes, offset];
22
+ const record = this.memory[position + this.offset - 1] ??= {};
23
+ assert(!record[`${syntax}:${state}`]);
24
+ record[`${syntax}:${state}`] = [nodes.slice(), offset];
25
+ //console.log('set', position + this.offset, syntax, state);
26
26
  }
27
27
  public clear(position: number): void {
28
- splice(this.memory, position, this.memory.length - position);
28
+ const memory = this.memory;
29
+ for (let i = position + this.offset, len = memory.length; i < len; ++i) {
30
+ memory.pop();
31
+ }
32
+ //console.log(position);
29
33
  }
30
34
  }
@@ -65,10 +65,8 @@ export function syntax<T>(syntax: number, precedence: number, cost: number | Par
65
65
  }
66
66
  return (source, context) => {
67
67
  if (source === '') return;
68
- const r = context.rule ?? 0;
69
- context.rule = r | syntax;
70
68
  context.backtrackable ??= ~0;
71
- context.state ??= 0;
69
+ const state = context.state ??= 0;
72
70
  const p = context.precedence;
73
71
  context.precedence = precedence;
74
72
  const { resources = { budget: 1, recursion: 1 } } = context;
@@ -76,7 +74,7 @@ export function syntax<T>(syntax: number, precedence: number, cost: number | Par
76
74
  if (resources.recursion <= 0) throw new Error('Too much recursion');
77
75
  --resources.recursion;
78
76
  const pos = source.length;
79
- const cache = context.memo?.get(pos, context.rule, syntax, context.state);
77
+ const cache = syntax && context.memo?.get(pos, syntax, state);
80
78
  const result: Result<T> = cache
81
79
  ? [cache[0], source.slice(cache[1])]
82
80
  : parser!(source, context);
@@ -86,20 +84,19 @@ export function syntax<T>(syntax: number, precedence: number, cost: number | Par
86
84
  assert(cost = cost as number);
87
85
  resources.budget -= cost;
88
86
  }
89
- if (syntax ) {
90
- if (r & context.backtrackable) {
87
+ if (syntax) {
88
+ if (state & context.backtrackable) {
91
89
  context.memo ??= new Memo();
92
- cache ?? context.memo.set(pos, context.rule, syntax, context.state, eval(result), source.length - exec(result).length);
93
- assert.deepStrictEqual(cache && cache, cache && context.memo.get(pos, context.rule, syntax, context.state));
90
+ cache ?? context.memo.set(pos, syntax, state, eval(result), source.length - exec(result).length);
91
+ assert.deepStrictEqual(cache && cache, cache && context.memo.get(pos, syntax, state));
94
92
  }
95
93
  else if (context.memo?.length! >= pos) {
96
- assert(!(r & context.backtrackable));
94
+ assert(!(state & context.backtrackable));
97
95
  context.memo!.clear(pos);
98
96
  }
99
97
  }
100
98
  }
101
99
  context.precedence = p;
102
- context.rule = r;
103
100
  return result;
104
101
  };
105
102
  }
@@ -2,8 +2,8 @@ import { undefined } from 'spica/global';
2
2
  import { Parser, Ctx, Tree, Context, SubParsers, SubTree, eval, exec, check } from '../parser';
3
3
  import { push } from 'spica/array';
4
4
 
5
- export function inits<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
- export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
5
+ export function inits<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
+ export function inits<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
7
  assert(parsers.every(f => f));
8
8
  if (parsers.length === 1) return parsers[0];
9
9
  return (source, context) => {
@@ -19,6 +19,7 @@ export function inits<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
19
19
  ? push(nodes, eval(result))
20
20
  : eval(result);
21
21
  rest = exec(result);
22
+ if (resume?.(eval(result), exec(result)) === false) break;
22
23
  }
23
24
  assert(rest.length <= source.length);
24
25
  return nodes && rest.length < source.length
@@ -2,8 +2,8 @@ import { undefined } from 'spica/global';
2
2
  import { Parser, Ctx, Tree, Context, SubParsers, SubTree, eval, exec, check } from '../parser';
3
3
  import { push } from 'spica/array';
4
4
 
5
- export function sequence<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
- export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
5
+ export function sequence<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
+ export function sequence<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
7
  assert(parsers.every(f => f));
8
8
  if (parsers.length === 1) return parsers[0];
9
9
  return (source, context) => {
@@ -19,6 +19,7 @@ export function sequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D
19
19
  ? push(nodes, eval(result))
20
20
  : eval(result);
21
21
  rest = exec(result);
22
+ if (resume?.(eval(result), exec(result)) === false) return;
22
23
  }
23
24
  assert(rest.length <= source.length);
24
25
  return nodes && rest.length < source.length
@@ -2,12 +2,12 @@ import { Parser, Ctx, Tree, Context, SubParsers, SubTree } from '../parser';
2
2
  import { union } from './union';
3
3
  import { inits } from './inits';
4
4
 
5
- export function subsequence<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
- export function subsequence<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
5
+ export function subsequence<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
+ export function subsequence<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
7
  assert(parsers.every(f => f));
8
8
  return union(
9
9
  parsers.map((_, i) =>
10
10
  i + 1 < parsers.length
11
- ? inits([parsers[i], subsequence(parsers.slice(i + 1))])
11
+ ? inits([parsers[i], subsequence(parsers.slice(i + 1), resume)], resume)
12
12
  : parsers[i]) as D);
13
13
  }
@@ -2,7 +2,7 @@ import { Parser, Ctx, Tree, Context, SubParsers, SubTree } from '../parser';
2
2
  import { union } from './union';
3
3
  import { sequence } from './sequence';
4
4
 
5
- export function tails<P extends Parser<unknown>>(parsers: SubParsers<P>): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
- export function tails<T, D extends Parser<T>[]>(parsers: D): Parser<T, Ctx, D> {
7
- return union(parsers.map((_, i) => sequence(parsers.slice(i))) as D);
5
+ export function tails<P extends Parser<unknown>>(parsers: SubParsers<P>, resume?: (nodes: SubTree<P>[], rest: string) => boolean): SubTree<P> extends Tree<P> ? P : Parser<SubTree<P>, Context<P>, SubParsers<P>>;
6
+ export function tails<T, D extends Parser<T>[]>(parsers: D, resume?: (nodes: T[], rest: string) => boolean): Parser<T, Ctx, D> {
7
+ return union(parsers.map((_, i) => sequence(parsers.slice(i), resume)) as D);
8
8
  }
@@ -15,7 +15,6 @@ export interface Ctx {
15
15
  precedence?: number;
16
16
  delimiters?: Delimiters;
17
17
  state?: number;
18
- rule?: number;
19
18
  backtrackable?: number;
20
19
  memo?: Memo;
21
20
  }
@@ -234,6 +234,15 @@ describe('Unit: parser/api/parse', () => {
234
234
  });
235
235
 
236
236
  it('recursion', () => {
237
+ assert.deepStrictEqual(
238
+ [...parse('{'.repeat(20)).children].map(el => el.outerHTML),
239
+ [`<p>${'{'.repeat(20)}</p>`]);
240
+ assert.deepStrictEqual(
241
+ [...parse('{'.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
242
+ [
243
+ '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
244
+ `<pre class="error" translate="no">${'{'.repeat(21)}</pre>`,
245
+ ]);
237
246
  assert.deepStrictEqual(
238
247
  [...parse('('.repeat(20)).children].map(el => el.outerHTML),
239
248
  [`<p>${'('.repeat(20)}</p>`]);
@@ -253,14 +262,8 @@ describe('Unit: parser/api/parse', () => {
253
262
  `<pre class="error" translate="no">${'['.repeat(21)}</pre>`,
254
263
  ]);
255
264
  assert.deepStrictEqual(
256
- [...parse('{'.repeat(20)).children].map(el => el.outerHTML),
257
- [`<p>${'{'.repeat(20)}</p>`]);
258
- assert.deepStrictEqual(
259
- [...parse('{'.repeat(21)).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
260
- [
261
- '<h1 id="error:rnd" class="error">Error: Too much recursion</h1>',
262
- `<pre class="error" translate="no">${'{'.repeat(21)}</pre>`,
263
- ]);
265
+ [...parse('['.repeat(17) + '\na').children].map(el => el.outerHTML),
266
+ [`<p>${'['.repeat(17)}<br>a</p>`]);
264
267
  });
265
268
 
266
269
  if (!navigator.userAgent.includes('Chrome')) return;
@@ -14,12 +14,6 @@ export const enum Rule {
14
14
  quote = 1 << 0,
15
15
  none = 0,
16
16
  }
17
- export const backtrackable = 0
18
- | Rule.annotation
19
- | Rule.reference
20
- | Rule.index
21
- | Rule.link
22
- | Rule.media;
23
17
 
24
18
  export const enum State {
25
19
  annotation = 1 << 6,
@@ -30,3 +24,9 @@ export const enum State {
30
24
  media = 1 << 1,
31
25
  autolink = 1 << 0,
32
26
  }
27
+ export const backtrackable = 0
28
+ | State.annotation
29
+ | State.reference
30
+ | State.index
31
+ | State.link
32
+ | State.media;
@@ -18,11 +18,11 @@ describe('Unit: parser/inline/annotation', () => {
18
18
  assert.deepStrictEqual(inspect(parser('((\n))')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('((\na))')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('((\\\na))')), 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*))']);
21
+ assert.deepStrictEqual(inspect(parser('((a\n))')), undefined);
22
+ assert.deepStrictEqual(inspect(parser('((a\\\n))')), undefined);
23
+ assert.deepStrictEqual(inspect(parser('((a\nb))')), undefined);
24
+ assert.deepStrictEqual(inspect(parser('((a\\\nb))')), undefined);
25
+ assert.deepStrictEqual(inspect(parser('((*a\nb*))')), undefined);
26
26
  assert.deepStrictEqual(inspect(parser('((\\))')), undefined);
27
27
  assert.deepStrictEqual(inspect(parser('((a)b))')), undefined);
28
28
  assert.deepStrictEqual(inspect(parser('(((a))')), undefined);
@@ -17,4 +17,4 @@ export const annotation: AnnotationParser = lazy(() => validate('((', syntax(Rul
17
17
  '))',
18
18
  false,
19
19
  ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest],
20
- ([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest)))));
20
+ ([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest, next)))));
@@ -45,6 +45,7 @@ describe('Unit: parser/inline/link', () => {
45
45
  it('invalid', () => {
46
46
  assert.deepStrictEqual(inspect(parser('')), undefined);
47
47
  assert.deepStrictEqual(inspect(parser('{}')), undefined);
48
+ assert.deepStrictEqual(inspect(parser('[{b}')), [['', '[', '<a href="b">b</a>'], '']);
48
49
  assert.deepStrictEqual(inspect(parser('[]')), undefined);
49
50
  assert.deepStrictEqual(inspect(parser('[]{}')), undefined);
50
51
  assert.deepStrictEqual(inspect(parser('[]{ }')), undefined);
@@ -64,7 +65,7 @@ describe('Unit: parser/inline/link', () => {
64
65
  assert.deepStrictEqual(inspect(parser('[\\ ]{b}')), undefined);
65
66
  assert.deepStrictEqual(inspect(parser('[\\\n]{b}')), undefined);
66
67
  assert.deepStrictEqual(inspect(parser('[&Tab;]{b}')), undefined);
67
- assert.deepStrictEqual(inspect(parser('[[]{b}')), undefined);
68
+ assert.deepStrictEqual(inspect(parser('[[]{b}')), [['', '[', '<a href="b">b</a>'], '']);
68
69
  assert.deepStrictEqual(inspect(parser('[]]{b}')), undefined);
69
70
  assert.deepStrictEqual(inspect(parser('[a]{}')), undefined);
70
71
  assert.deepStrictEqual(inspect(parser('[a\nb]{b}')), undefined);
@@ -31,16 +31,15 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], syntax(Rule.link
31
31
  ']',
32
32
  true,
33
33
  undefined,
34
- ([, ns = [], rest], next) => next[0] === ']' ? undefined : optimize('[', ns, rest)),
34
+ ([, ns = [], rest], next) => next[0] === ']' ? undefined : optimize('[', ns, rest, next)),
35
35
  ]))),
36
- // 全体の失敗が確定した時も解析し予算を浪費している
37
36
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
38
- ]),
37
+ ], nodes => nodes[0][0] !== ''),
39
38
  ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []])),
40
39
  ([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
41
- if (params.length === 0) return;
42
40
  assert(content[0] !== '' || params.length === 0);
43
41
  if (content[0] === '') return [content, rest];
42
+ if (params.length === 0) return;
44
43
  assert(params.every(p => typeof p === 'string'));
45
44
  if (content.length !== 0 && trimNode(content).length === 0) return;
46
45
  if (eval(some(autolink)(stringify(content), context))?.some(node => typeof node === 'object')) return;
@@ -187,11 +186,15 @@ function decode(uri: string): string {
187
186
  }
188
187
  }
189
188
 
190
- export function optimize(opener: string, ns: readonly (string | HTMLElement)[], rest: string): Result<string> {
189
+ export function optimize(opener: string, ns: readonly (string | HTMLElement)[], rest: string, next: string): Result<string> {
190
+ if (next[+(next[0] === '\\')] === '\n') return;
191
191
  let count = 0;
192
192
  for (let i = 0; i < ns.length - 1; i += 2) {
193
- if (ns[i] !== '' || ns[i + 1] !== opener[0]) break;
194
- ++count;
193
+ const fst = ns[i];
194
+ const snd = ns[i + 1] as string;
195
+ assert(typeof snd === 'string');
196
+ if (fst !== '' || snd[0] !== opener[0]) break;
197
+ count += snd.length;
195
198
  }
196
199
  return [['', opener[0].repeat(opener.length + count)], rest.slice(count)];
197
200
  }
@@ -22,6 +22,7 @@ describe('Unit: parser/inline/media', () => {
22
22
  assert.deepStrictEqual(inspect(parser('{}')), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('[]')), undefined);
24
24
  assert.deepStrictEqual(inspect(parser('!{}')), undefined);
25
+ assert.deepStrictEqual(inspect(parser('![{b}')), undefined);
25
26
  assert.deepStrictEqual(inspect(parser('![]')), undefined);
26
27
  assert.deepStrictEqual(inspect(parser('![]{}')), undefined);
27
28
  assert.deepStrictEqual(inspect(parser('![]{ }')), undefined);
@@ -18,11 +18,11 @@ describe('Unit: parser/inline/reference', () => {
18
18
  assert.deepStrictEqual(inspect(parser('[[\n]]')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('[[\na]]')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('[[\\\na]]')), 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*]]']);
21
+ assert.deepStrictEqual(inspect(parser('[[a\n]]')), undefined);
22
+ assert.deepStrictEqual(inspect(parser('[[a\\\n]]')), undefined);
23
+ assert.deepStrictEqual(inspect(parser('[[a\nb]]')), undefined);
24
+ assert.deepStrictEqual(inspect(parser('[[a\\\nb]]')), undefined);
25
+ assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), undefined);
26
26
  assert.deepStrictEqual(inspect(parser('[[\\]]')), undefined);
27
27
  assert.deepStrictEqual(inspect(parser('[[a]b]]')), undefined);
28
28
  assert.deepStrictEqual(inspect(parser('[[[a]]')), undefined);
@@ -23,7 +23,7 @@ export const reference: ReferenceParser = lazy(() => validate('[[', syntax(Rule.
23
23
  ']]',
24
24
  false,
25
25
  ([, ns], rest) => [[html('sup', attributes(ns), [html('span', trimNode(defrag(ns)))])], rest],
26
- ([, ns, rest], next) => next[0] === ']' ? undefined : optimize('[[', ns, rest)))));
26
+ ([, ns, rest], next) => next[0] === ']' ? undefined : optimize('[[', ns, rest, next)))));
27
27
 
28
28
  const abbr: ReferenceParser.AbbrParser = creator(bind(surround(
29
29
  '^',
@@ -8,6 +8,7 @@ describe('Unit: parser/inline/ruby', () => {
8
8
 
9
9
  it('invalid', () => {
10
10
  assert.deepStrictEqual(inspect(parser('')), undefined);
11
+ assert.deepStrictEqual(inspect(parser('[(b)')), undefined);
11
12
  assert.deepStrictEqual(inspect(parser('[]()')), undefined);
12
13
  assert.deepStrictEqual(inspect(parser('[](b)')), undefined);
13
14
  assert.deepStrictEqual(inspect(parser('[ ](b)')), undefined);
@@ -10,7 +10,7 @@ import { unshift } from 'spica/array';
10
10
  export const template: TemplateParser = lazy(() => syntax(Rule.none, 2, surround(
11
11
  '{{', some(union([bracket, escsource]), '}'), '}}', true,
12
12
  ([, ns = []], rest) => [[html('span', { class: 'template' }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest],
13
- ([, ns = [], rest], next) => next[0] === '}' ? undefined : optimize('{{', ns, rest))));
13
+ ([, ns = [], rest], next) => next[0] === '}' ? undefined : optimize('{{', ns, rest, next))));
14
14
 
15
15
  const bracket: TemplateParser.BracketParser = lazy(() => creator(union([
16
16
  surround(str('('), some(union([bracket, escsource]), ')'), str(')'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
@@ -159,6 +159,7 @@ describe('Unit: parser/inline', () => {
159
159
  assert.deepStrictEqual(inspect(parser('[[${]]}$')), [['', '[[', '<span class="math" translate="no" data-src="${]]}$">${]]}$</span>'], '']);
160
160
  assert.deepStrictEqual(inspect(parser('"[[""]]')), [['"', '<sup class="reference"><span>""</span></sup>'], '']);
161
161
  assert.deepStrictEqual(inspect(parser('[[a](b)]{c}')), [['<a href="c"><ruby>a<rp>(</rp><rt>b</rt><rp>)</rp></ruby></a>'], '']);
162
+ assert.deepStrictEqual(inspect(parser('[[[[[[[{a}')), [['', '[[[[[[[', '<a href="a">a</a>'], '']);
162
163
  assert.deepStrictEqual(inspect(parser('<http://host>')), [['<', '<a href="http://host" target="_blank">http://host</a>', '>'], '']);
163
164
  assert.deepStrictEqual(inspect(parser('[~http://host')), [['', '[', '~', '<a href="http://host" target="_blank">http://host</a>'], '']);
164
165
  assert.deepStrictEqual(inspect(parser('[~a@b')), [['', '[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
@@ -167,7 +168,7 @@ describe('Unit: parser/inline', () => {
167
168
  assert.deepStrictEqual(inspect(parser('[^a@b')), [['[^', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
168
169
  assert.deepStrictEqual(inspect(parser('[#a*b\nc*]')), [['[', '<a href="/hashtags/a" class="hashtag">#a</a>', '<em>b<br>c</em>', ']'], '']);
169
170
  assert.deepStrictEqual(inspect(parser('[*a\nb*]{/}')), [['[', '<em>a<br>b</em>', ']', '<a href="/">/</a>'], '']);
170
- assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['', '[[', '<em>a<br>b</em>', ']', ']'], '']);
171
+ assert.deepStrictEqual(inspect(parser('[[*a\nb*]]')), [['[', '[', '<em>a<br>b</em>', ']', ']'], '']);
171
172
  assert.deepStrictEqual(inspect(parser('"[% *"*"*')), [['"', '[%', ' ', '*', '"', '*', '"', '*'], '']);
172
173
  assert.deepStrictEqual(inspect(parser('"[% "*"* %]')), [['"', '[%', ' ', '"', '*', '"', '*', ' ', '%', ']'], '']);
173
174
  });