securemark 0.260.3 → 0.260.6

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.260.6
4
+
5
+ - Refactoring.
6
+
7
+ ## 0.260.5
8
+
9
+ - Refactoring.
10
+
11
+ ## 0.260.4
12
+
13
+ - Refactoring.
14
+
3
15
  ## 0.260.3
4
16
 
5
17
  - Refactoring.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.260.3 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.260.6 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"));
@@ -64,9 +64,9 @@ __exportStar(__webpack_require__(256), exports);
64
64
  Object.defineProperty(exports, "__esModule", ({
65
65
  value: true
66
66
  }));
67
- exports.ObjectSetPrototypeOf = exports.ObjectGetPrototypeOf = exports.ObjectCreate = exports.ObjectAssign = exports.toString = exports.isEnumerable = exports.isPrototypeOf = exports.hasOwnProperty = exports.isArray = exports.sign = exports.round = exports.random = exports.min = exports.max = exports.floor = exports.ceil = exports.abs = exports.parseInt = exports.parseFloat = exports.isSafeInteger = exports.isNaN = exports.isInteger = exports.isFinite = exports[NaN] = void 0;
67
+ exports.ObjectSetPrototypeOf = exports.ObjectGetPrototypeOf = exports.ObjectCreate = exports.ObjectAssign = exports.toString = exports.isEnumerable = exports.isPrototypeOf = exports.hasOwnProperty = exports.isArray = exports.sqrt = exports.log = exports.tan = exports.cos = exports.sign = exports.round = exports.random = exports.min = exports.max = exports.floor = exports.ceil = exports.abs = exports.parseInt = exports.parseFloat = exports.isSafeInteger = exports.isNaN = exports.isInteger = exports.isFinite = exports[NaN] = void 0;
68
68
  exports[NaN] = Number.NaN, exports.isFinite = Number.isFinite, exports.isInteger = Number.isInteger, exports.isNaN = Number.isNaN, exports.isSafeInteger = Number.isSafeInteger, exports.parseFloat = Number.parseFloat, exports.parseInt = Number.parseInt;
69
- exports.abs = Math.abs, exports.ceil = Math.ceil, exports.floor = Math.floor, exports.max = Math.max, exports.min = Math.min, exports.random = Math.random, exports.round = Math.round, exports.sign = Math.sign;
69
+ exports.abs = Math.abs, exports.ceil = Math.ceil, exports.floor = Math.floor, exports.max = Math.max, exports.min = Math.min, exports.random = Math.random, exports.round = Math.round, exports.sign = Math.sign, exports.cos = Math.cos, exports.tan = Math.tan, exports.log = Math.log, exports.sqrt = Math.sqrt;
70
70
  exports.isArray = Array.isArray;
71
71
  exports.hasOwnProperty = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
72
72
  exports.isPrototypeOf = Object.prototype.isPrototypeOf.call.bind(Object.prototype.isPrototypeOf);
@@ -356,6 +356,8 @@ exports.Cache = void 0;
356
356
 
357
357
  const global_1 = __webpack_require__(4128);
358
358
 
359
+ const alias_1 = __webpack_require__(5406);
360
+
359
361
  const clock_1 = __webpack_require__(7681);
360
362
 
361
363
  const invlist_1 = __webpack_require__(7452);
@@ -364,20 +366,23 @@ const heap_1 = __webpack_require__(818);
364
366
 
365
367
  const assign_1 = __webpack_require__(4401);
366
368
 
367
- const tuple_1 = __webpack_require__(5341);
368
-
369
369
  class Cache {
370
370
  constructor(capacity, opts = {}) {
371
371
  this.settings = {
372
+ window: 0,
372
373
  capacity: 0,
373
374
  space: global_1.Infinity,
374
375
  age: global_1.Infinity,
375
376
  earlyExpiring: false,
376
- limit: 950,
377
377
  capture: {
378
378
  delete: true,
379
379
  clear: true
380
- }
380
+ },
381
+ resolution: 1,
382
+ offset: 0,
383
+ block: 20,
384
+ sweep: 10,
385
+ limit: 950
381
386
  };
382
387
  this.overlap = 0;
383
388
  this.SIZE = 0;
@@ -387,31 +392,8 @@ class Cache {
387
392
  LFU: new invlist_1.List()
388
393
  };
389
394
  this.expiries = new heap_1.Heap((a, b) => a.value.expiry - b.value.expiry);
390
- this.stats = {
391
- LRU: (0, tuple_1.tuple)(0, 0),
392
- LFU: (0, tuple_1.tuple)(0, 0),
393
-
394
- slide() {
395
- const {
396
- LRU,
397
- LFU
398
- } = this;
399
- LRU[1] = LRU[0];
400
- LRU[0] = 0;
401
- LFU[1] = LFU[0];
402
- LFU[0] = 0;
403
- },
404
-
405
- clear() {
406
- const {
407
- LRU,
408
- LFU
409
- } = this;
410
- LRU[0] = LRU[1] = 0;
411
- LFU[0] = LFU[1] = 0;
412
- }
413
-
414
- };
395
+ this.misses = 0;
396
+ this.sweep = 0;
415
397
  this.ratio = 500;
416
398
 
417
399
  if (typeof capacity === 'object') {
@@ -419,15 +401,19 @@ class Cache {
419
401
  capacity = opts.capacity ?? 0;
420
402
  }
421
403
 
422
- (0, assign_1.extend)(this.settings, opts, {
404
+ const settings = (0, assign_1.extend)(this.settings, opts, {
423
405
  capacity
424
406
  });
425
- this.capacity = this.settings.capacity;
407
+ this.capacity = settings.capacity;
426
408
  if (this.capacity >= 1 === false) throw new Error(`Spica: Cache: Capacity must be 1 or more.`);
427
- this.space = this.settings.space;
428
- this.limit = this.settings.limit;
429
- this.earlyExpiring = this.settings.earlyExpiring;
430
- this.disposer = this.settings.disposer;
409
+ this.window = settings.window || this.capacity;
410
+ if (this.window * 1000 < this.capacity) throw new Error(`Spica: Cache: Window must be 0.1% of capacity or more.`);
411
+ this.space = settings.space;
412
+ this.block = settings.block;
413
+ this.limit = settings.limit;
414
+ this.earlyExpiring = settings.earlyExpiring;
415
+ this.disposer = settings.disposer;
416
+ this.stats = new Stats(this.window, settings.resolution, settings.offset);
431
417
  }
432
418
 
433
419
  get length() {
@@ -452,7 +438,7 @@ class Cache {
452
438
 
453
439
  ensure(margin, skip) {
454
440
  let size = skip?.value.size ?? 0;
455
- if (margin - size <= 0) return;
441
+ if (margin - size <= 0) return true;
456
442
  const {
457
443
  LRU,
458
444
  LFU
@@ -474,7 +460,6 @@ class Cache {
474
460
  target = LFU.last !== skip ? LFU.last : LFU.length >= 2 ? LFU.last.prev : skip;
475
461
 
476
462
  if (target !== skip) {
477
- if (this.ratio > 500) break;
478
463
  LRU.unshiftNode(target);
479
464
  ++this.overlap;
480
465
  }
@@ -482,6 +467,18 @@ class Cache {
482
467
  // fallthrough
483
468
 
484
469
  default:
470
+ if (this.misses * 100 > LRU.length * this.block) {
471
+ this.sweep ||= LRU.length * this.settings.sweep / 100 + 1 | 0;
472
+
473
+ if (this.sweep > 0) {
474
+ LRU.head = LRU.head.next.next;
475
+ --this.sweep;
476
+ this.sweep ||= -(0, alias_1.round)(LRU.length * this.settings.sweep / 100 * 99 / 100);
477
+ } else {
478
+ ++this.sweep;
479
+ }
480
+ }
481
+
485
482
  target = LRU.last !== skip ? LRU.last : LRU.length >= 2 ? LRU.last.prev : LFU.last;
486
483
  }
487
484
 
@@ -489,6 +486,8 @@ class Cache {
489
486
  skip = skip?.list && skip;
490
487
  size = skip?.value.size ?? 0;
491
488
  }
489
+
490
+ return !!skip?.list;
492
491
  }
493
492
 
494
493
  put(key, value, {
@@ -503,10 +502,9 @@ class Cache {
503
502
  const expiry = age === global_1.Infinity ? global_1.Infinity : (0, clock_1.now)() + age;
504
503
  const node = this.memory.get(key);
505
504
 
506
- if (node) {
505
+ if (node && this.ensure(size, node)) {
507
506
  const val = node.value.value;
508
507
  const index = node.value;
509
- this.ensure(size, node);
510
508
  this.SIZE += size - index.size;
511
509
  index.size = size;
512
510
  index.expiry = expiry;
@@ -550,10 +548,16 @@ class Cache {
550
548
 
551
549
  get(key) {
552
550
  const node = this.memory.get(key);
553
- if (!node) return;
551
+
552
+ if (!node) {
553
+ ++this.misses;
554
+ return;
555
+ }
556
+
554
557
  const expiry = node.value.expiry;
555
558
 
556
559
  if (expiry !== global_1.Infinity && expiry < (0, clock_1.now)()) {
560
+ ++this.misses;
557
561
  this.evict(node, true);
558
562
  return;
559
563
  } // Optimization for memoize.
@@ -561,7 +565,9 @@ class Cache {
561
565
 
562
566
  if (this.capacity > 3 && node === node.list.head) return node.value.value;
563
567
  this.access(node);
564
- this.slide();
568
+ this.adjust();
569
+ this.misses &&= 0;
570
+ this.sweep = 0;
565
571
  return node.value.value;
566
572
  }
567
573
 
@@ -588,6 +594,8 @@ class Cache {
588
594
  }
589
595
 
590
596
  clear() {
597
+ this.misses = 0;
598
+ this.sweep = 0;
591
599
  this.overlap = 0;
592
600
  this.SIZE = 0;
593
601
  this.ratio = 500;
@@ -620,31 +628,26 @@ class Cache {
620
628
  return;
621
629
  }
622
630
 
623
- slide() {
624
- const {
625
- LRU,
626
- LFU
627
- } = this.stats;
631
+ adjust() {
628
632
  const {
629
633
  capacity,
630
634
  ratio,
631
635
  limit,
636
+ stats,
632
637
  indexes
633
638
  } = this;
634
- const window = capacity;
635
- const total = LRU[0] + LFU[0];
636
- total === window && this.stats.slide();
637
- if (total * 1000 % capacity || !LRU[1] || !LFU[1]) return;
639
+ if (stats.subtotal() * 1000 % capacity || !stats.full()) return;
638
640
  const lenR = indexes.LRU.length;
639
641
  const lenF = indexes.LFU.length;
640
- const lenV = this.overlap;
641
- const r = (lenF + lenV) * 1000 / (lenR + lenF || 1) | 0;
642
- const rateR0 = rate(window, LRU[0], LRU[0] + LFU[0], LRU[1], LRU[1] + LFU[1], 0) * r;
643
- const rateF0 = rate(window, LFU[0], LRU[0] + LFU[0], LFU[1], LRU[1] + LFU[1], 0) * (1000 - r);
644
- const rateF1 = rate(window, LFU[1], LRU[1] + LFU[1], LFU[0], LRU[0] + LFU[0], 5) * (1000 - r); // 操作頻度を超えてキャッシュ比率を増減させても余剰比率の消化が追いつかず無駄
642
+ const lenO = this.overlap;
643
+ const leverage = (lenF + lenO) * 1000 / (lenR + lenF) | 0;
644
+ const rateR0 = stats.rateLRU() * leverage;
645
+ const rateF0 = stats.rateLFU() * (1000 - leverage);
646
+ const rateF1 = stats.offset && stats.rateLFU(true) * (1000 - leverage); // 操作頻度を超えてキャッシュ比率を増減させても余剰比率の消化が追いつかず無駄
645
647
  // LRUの下限設定ではLRU拡大の要否を迅速に判定できないためLFUのヒット率低下の検出で代替する
646
648
 
647
- if (ratio > 0 && (rateR0 > rateF0 || rateF0 < rateF1 * 0.95)) {
649
+ if (ratio > 0 && (rateR0 > rateF0 || stats.offset && rateF0 * 100 < rateF1 * (100 - stats.offset))) {
650
+ //rateR0 <= rateF0 && rateF0 * 100 < rateF1 * (100 - stats.offset) && console.debug(0);
648
651
  if (lenR >= capacity * (1000 - ratio) / 1000) {
649
652
  //ratio % 100 || ratio === 1000 || console.debug('-', ratio, LRU, LFU);
650
653
  --this.ratio;
@@ -682,13 +685,100 @@ class Cache {
682
685
 
683
686
  exports.Cache = Cache;
684
687
 
685
- function rate(window, currHits, currTotal, prevHits, prevTotal, offset) {
686
- const prevRate = prevHits * 100 / prevTotal | 0;
687
- const currRatio = currTotal * 100 / window - offset | 0;
688
- if (currRatio <= 0) return prevRate * 100;
689
- const currRate = currHits * 100 / currTotal | 0;
690
- const prevRatio = 100 - currRatio;
691
- return currRate * currRatio + prevRate * prevRatio;
688
+ class Stats {
689
+ constructor(window, resolution, offset) {
690
+ this.window = window;
691
+ this.resolution = resolution;
692
+ this.offset = offset;
693
+ this.max = (0, alias_1.ceil)(this.resolution * (100 + this.offset) / 100) + 1;
694
+ this.LRU = [0];
695
+ this.LFU = [0];
696
+ }
697
+
698
+ get length() {
699
+ return this.LRU.length;
700
+ }
701
+
702
+ full() {
703
+ return this.length === this.max;
704
+ }
705
+
706
+ rateLRU(offset = false) {
707
+ return rate(this.window, this.LRU, this.LFU, +offset && this.offset);
708
+ }
709
+
710
+ rateLFU(offset = false) {
711
+ return rate(this.window, this.LFU, this.LRU, +offset && this.offset);
712
+ }
713
+
714
+ subtotal() {
715
+ const {
716
+ LRU,
717
+ LFU,
718
+ window,
719
+ resolution,
720
+ offset
721
+ } = this;
722
+
723
+ if (offset && LRU[0] + LFU[0] >= window * offset / 100) {
724
+ if (this.length === 1) {
725
+ this.slide();
726
+ } else {
727
+ LRU[1] += LRU[0];
728
+ LFU[1] += LFU[0];
729
+ LRU[0] = 0;
730
+ LFU[0] = 0;
731
+ }
732
+ }
733
+
734
+ const subtotal = LRU[+offset && 1] + LFU[+offset && 1] || 0;
735
+ subtotal >= window / resolution && this.slide();
736
+ return LRU[0] + LFU[0];
737
+ }
738
+
739
+ slide() {
740
+ const {
741
+ LRU,
742
+ LFU,
743
+ max
744
+ } = this;
745
+
746
+ if (LRU.length === max) {
747
+ LRU.pop();
748
+ LFU.pop();
749
+ }
750
+
751
+ LRU.unshift(0);
752
+ LFU.unshift(0);
753
+ }
754
+
755
+ clear() {
756
+ this.LRU = [0];
757
+ this.LFU = [0];
758
+ }
759
+
760
+ }
761
+
762
+ function rate(window, hits1, hits2, offset) {
763
+ let total = 0;
764
+ let hits = 0;
765
+ let ratio = 100;
766
+
767
+ for (let i = 0, len = hits1.length; i < len; ++i) {
768
+ const subtotal = hits1[i] + hits2[i];
769
+ if (subtotal === 0) continue;
770
+ offset = i + 1 === len ? 0 : offset;
771
+ const subratio = (0, alias_1.min)(subtotal * 100 / window, ratio) - offset;
772
+ offset = offset && subratio < 0 ? -subratio : 0;
773
+ if (subratio <= 0) continue;
774
+ const rate = window * subratio / subtotal;
775
+ total += subtotal * rate;
776
+ hits += hits1[i] * rate;
777
+ ratio -= subratio;
778
+ if (ratio <= 0) break;
779
+ }
780
+
781
+ return hits * 10000 / total | 0;
692
782
  }
693
783
 
694
784
  /***/ }),
@@ -1671,25 +1761,6 @@ function random(len) {
1671
1761
 
1672
1762
  /***/ }),
1673
1763
 
1674
- /***/ 5341:
1675
- /***/ ((__unused_webpack_module, exports) => {
1676
-
1677
- "use strict";
1678
-
1679
-
1680
- Object.defineProperty(exports, "__esModule", ({
1681
- value: true
1682
- }));
1683
- exports.tuple = void 0;
1684
-
1685
- function tuple(...as) {
1686
- return as;
1687
- }
1688
-
1689
- exports.tuple = tuple;
1690
-
1691
- /***/ }),
1692
-
1693
1764
  /***/ 5177:
1694
1765
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
1695
1766
 
@@ -2888,12 +2959,16 @@ const parser_1 = __webpack_require__(6728);
2888
2959
  const memo_1 = __webpack_require__(1090);
2889
2960
 
2890
2961
  function reset(base, parser) {
2962
+ if (!('memo' in base)) {
2963
+ base.memo = global_1.undefined;
2964
+ }
2965
+
2891
2966
  const changes = global_1.Object.entries(base);
2892
2967
  const values = (0, global_1.Array)(changes.length);
2893
2968
  return ({
2894
2969
  source,
2895
2970
  context
2896
- }) => apply(parser, source, (0, alias_1.ObjectCreate)(context), changes, values);
2971
+ }) => apply(parser, source, (0, alias_1.ObjectCreate)(context), changes, values, true);
2897
2972
  }
2898
2973
 
2899
2974
  exports.reset = reset;
@@ -2909,18 +2984,25 @@ function context(base, parser) {
2909
2984
 
2910
2985
  exports.context = context;
2911
2986
 
2912
- function apply(parser, source, context, changes, values) {
2987
+ function apply(parser, source, context, changes, values, reset = false) {
2913
2988
  if (context) for (let i = 0; i < changes.length; ++i) {
2914
2989
  const change = changes[i];
2915
2990
  const prop = change[0];
2916
2991
 
2917
2992
  switch (prop) {
2918
2993
  case 'resources':
2919
- if (prop in context && !(0, alias_1.hasOwnProperty)(context, prop)) break; // @ts-expect-error
2920
-
2994
+ if (!reset) break;
2995
+ if (prop in context && !(0, alias_1.hasOwnProperty)(context, prop)) break;
2921
2996
  context[prop] = (0, alias_1.ObjectCreate)(change[1]);
2922
2997
  break;
2923
2998
 
2999
+ case 'memo':
3000
+ if (!reset) break;
3001
+ context.memo = new memo_1.Memo({
3002
+ targets: context.memo?.targets
3003
+ });
3004
+ break;
3005
+
2924
3006
  default:
2925
3007
  values[i] = context[prop];
2926
3008
  context[prop] = change[1];
@@ -2935,8 +3017,11 @@ function apply(parser, source, context, changes, values) {
2935
3017
  const prop = change[0];
2936
3018
 
2937
3019
  switch (prop) {
2938
- case 'resources':
2939
- break;
3020
+ case 'resources': // @ts-expect-error
3021
+
3022
+ case 'memo':
3023
+ if (!reset) break;
3024
+ // fallthrough
2940
3025
 
2941
3026
  default:
2942
3027
  context[prop] = values[i];
@@ -2953,26 +3038,25 @@ function syntax(syntax, prec, cost, state, parser) {
2953
3038
  }) => {
2954
3039
  if (source === '') return;
2955
3040
  const memo = context.memo ??= new memo_1.Memo();
2956
- context.memorable ??= ~0;
2957
3041
  context.offset ??= 0;
2958
3042
  const position = source.length + context.offset;
2959
- const st0 = context.state ?? 0;
2960
- const st1 = context.state = st0 | state;
2961
- const cache = syntax && memo.get(position, syntax, st1);
3043
+ const stateOuter = context.state ?? 0;
3044
+ const stateInner = context.state = stateOuter | state;
3045
+ const cache = syntax && stateInner & memo.targets && memo.get(position, syntax, stateInner);
2962
3046
  const result = cache ? cache.length === 0 ? global_1.undefined : [cache[0], source.slice(cache[1])] : parser({
2963
3047
  source,
2964
3048
  context
2965
3049
  });
2966
3050
 
2967
- if (syntax && st0 & context.memorable) {
2968
- cache ?? memo.set(position, syntax, st1, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result, '').length);
3051
+ if (syntax && stateOuter & memo.targets) {
3052
+ cache ?? memo.set(position, syntax, stateInner, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result, '').length);
2969
3053
  }
2970
3054
 
2971
- if (result && !st0 && memo.length >= position + 2) {
3055
+ if (result && !stateOuter && memo.length >= position + 2) {
2972
3056
  memo.clear(position + 2);
2973
3057
  }
2974
3058
 
2975
- context.state = st0;
3059
+ context.state = stateOuter;
2976
3060
  return result;
2977
3061
  }));
2978
3062
  }
@@ -3200,8 +3284,11 @@ Object.defineProperty(exports, "__esModule", ({
3200
3284
  exports.Memo = void 0;
3201
3285
 
3202
3286
  class Memo {
3203
- constructor() {
3287
+ constructor({
3288
+ targets = ~0
3289
+ } = {}) {
3204
3290
  this.memory = [];
3291
+ this.targets = targets;
3205
3292
  }
3206
3293
 
3207
3294
  get length() {
@@ -3209,14 +3296,14 @@ class Memo {
3209
3296
  }
3210
3297
 
3211
3298
  get(position, syntax, state) {
3212
- //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
3299
+ //console.log('get', position, syntax, state, this.memory[position - 1]?.[`${syntax}:${state}`]);;
3213
3300
  const cache = this.memory[position - 1]?.[`${syntax}:${state}`];
3214
3301
  return cache?.length === 2 ? [cache[0].slice(), cache[1]] : cache;
3215
3302
  }
3216
3303
 
3217
3304
  set(position, syntax, state, nodes, offset) {
3218
3305
  const record = this.memory[position - 1] ??= {};
3219
- record[`${syntax}:${state}`] = nodes ? [nodes.slice(), offset] : []; //console.log('set', position + this.offset, syntax, state, record[`${syntax}:${state}`]);
3306
+ record[`${syntax}:${state}`] = nodes ? [nodes.slice(), offset] : []; //console.log('set', position, syntax, state, record[`${syntax}:${state}`]);
3220
3307
  }
3221
3308
 
3222
3309
  clear(position) {
@@ -3224,7 +3311,7 @@ class Memo {
3224
3311
 
3225
3312
  for (let i = position, len = memory.length; i < len; ++i) {
3226
3313
  memory.pop();
3227
- } //console.log('clear', position + this.offset + 1);
3314
+ } //console.log('clear', position + 1);
3228
3315
 
3229
3316
  }
3230
3317
 
@@ -3595,6 +3682,8 @@ const global_1 = __webpack_require__(4128);
3595
3682
 
3596
3683
  const parser_1 = __webpack_require__(6728);
3597
3684
 
3685
+ const memo_1 = __webpack_require__(1090);
3686
+
3598
3687
  const segment_1 = __webpack_require__(9002);
3599
3688
 
3600
3689
  const header_1 = __webpack_require__(5702);
@@ -3616,9 +3705,11 @@ const array_1 = __webpack_require__(8112);
3616
3705
  function bind(target, settings) {
3617
3706
  let context = { ...settings,
3618
3707
  host: settings.host ?? new url_1.ReadonlyURL(global_1.location.pathname, global_1.location.origin),
3619
- memorable: 236
3620
- /* State.backtrackable */
3708
+ memo: new memo_1.Memo({
3709
+ targets: 236
3710
+ /* State.backtrackers */
3621
3711
 
3712
+ })
3622
3713
  };
3623
3714
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
3624
3715
  const blocks = [];
@@ -3958,6 +4049,8 @@ const global_1 = __webpack_require__(4128);
3958
4049
 
3959
4050
  const parser_1 = __webpack_require__(6728);
3960
4051
 
4052
+ const memo_1 = __webpack_require__(1090);
4053
+
3961
4054
  const segment_1 = __webpack_require__(9002);
3962
4055
 
3963
4056
  const header_1 = __webpack_require__(5702);
@@ -3988,9 +4081,11 @@ function parse(source, opts = {}, context) {
3988
4081
  ...(context?.resources && {
3989
4082
  resources: context.resources
3990
4083
  }),
3991
- memorable: 236
3992
- /* State.backtrackable */
4084
+ memo: new memo_1.Memo({
4085
+ targets: 236
4086
+ /* State.backtrackers */
3993
4087
 
4088
+ })
3994
4089
  };
3995
4090
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
3996
4091
  const node = (0, dom_1.frag)();
@@ -5478,15 +5573,16 @@ const source_1 = __webpack_require__(6743);
5478
5573
 
5479
5574
  const dom_1 = __webpack_require__(3252);
5480
5575
 
5481
- exports.cite = (0, combinator_1.creation)(1, false, (0, combinator_1.line)((0, combinator_1.fmap)((0, combinator_1.validate)('>>', (0, combinator_1.reverse)((0, combinator_1.tails)([(0, source_1.str)(/^>*(?=>>[^>\s]+[^\S\n]*(?:$|\n))/), (0, combinator_1.union)([anchor_1.anchor, // Subject page representation.
5576
+ exports.cite = (0, combinator_1.creation)(1, false, (0, combinator_1.line)((0, combinator_1.fmap)((0, combinator_1.validate)('>>', (0, combinator_1.reverse)((0, combinator_1.tails)([(0, source_1.str)(/^>*(?=>>[^>\s]+\s*$)/), (0, combinator_1.union)([anchor_1.anchor, // Subject page representation.
5482
5577
  // リンクの実装は後で検討
5483
- (0, combinator_1.focus)(/^>>\.[^\S\n]*(?:$|\n)/, () => [[(0, dom_1.html)('a', {
5578
+ (0, combinator_1.focus)(/^>>\.(?=\s*$)/, () => [[(0, dom_1.html)('a', {
5484
5579
  class: 'anchor'
5485
- }, '>>.')], '']), (0, combinator_1.focus)(/^>>#\S*[^\S\n]*(?:$|\n)/, ({
5580
+ }, '>>.')], '']), (0, combinator_1.focus)(/^>>#\S*(?=\s*$)/, ({
5486
5581
  source
5487
5582
  }) => [[(0, dom_1.html)('a', {
5488
5583
  class: 'anchor'
5489
- }, source)], '']), (0, combinator_1.focus)(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, ({
5584
+ }, source)], '']), // Support all domains, but don't support IP(v6) addresses.
5585
+ (0, combinator_1.focus)(/^>>https?:\/\/[^\p{C}\p{S}\p{P}\s]\S*(?=\s*$)/u, ({
5490
5586
  source
5491
5587
  }) => [[(0, dom_1.html)('a', {
5492
5588
  class: 'anchor',
@@ -5948,8 +6044,8 @@ exports.autolink = (0, combinator_1.fmap)((0, combinator_1.validate)(/^(?:[@#>0-
5948
6044
  /* State.autolink */
5949
6045
  , false, (0, combinator_1.syntax)(2
5950
6046
  /* Syntax.autolink */
5951
- , 1, 1, 0
5952
- /* State.none */
6047
+ , 1, 1, ~1
6048
+ /* State.shortcut */
5953
6049
  , (0, combinator_1.some)((0, combinator_1.union)([url_1.url, email_1.email, // Escape unmatched email-like strings.
5954
6050
  (0, source_1.str)(/^[0-9a-z]+(?:[.+_-][0-9a-z]+)*(?:@(?:[0-9a-z]+(?:[.-][0-9a-z]+)*)?)*/i), channel_1.channel, account_1.account, // Escape unmatched account-like strings.
5955
6051
  (0, source_1.str)(/^@+[0-9a-z]*(?:-[0-9a-z]+)*/i), // Escape invalid leading characters.
@@ -6502,10 +6598,12 @@ const dom_1 = __webpack_require__(3252);
6502
6598
 
6503
6599
  exports.index = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[#', (0, combinator_1.fmap)((0, indexee_1.indexee)((0, combinator_1.surround)('[#', (0, combinator_1.constraint)(32
6504
6600
  /* State.index */
6505
- , false, (0, combinator_1.syntax)(1024
6601
+ , false, (0, combinator_1.syntax)(2048
6506
6602
  /* Syntax.index */
6507
- , 2, 1, 254
6508
- /* State.linkable */
6603
+ , 2, 1, 250
6604
+ /* State.linkers */
6605
+ | 4
6606
+ /* State.media */
6509
6607
  , (0, visibility_1.startTight)((0, combinator_1.open)((0, source_1.stropt)(/^\|?/), (0, visibility_1.trimBlankEnd)((0, combinator_1.some)((0, combinator_1.union)([signature, inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]])), true)))), ']', false, ([, ns], rest) => [[(0, dom_1.html)('a', (0, dom_1.defrag)(ns))], rest])), ([el]) => [(0, dom_1.define)(el, {
6510
6608
  id: el.id ? null : global_1.undefined,
6511
6609
  class: 'index',
@@ -6707,8 +6805,8 @@ const array_1 = __webpack_require__(8112); // Don't use the symbols already used
6707
6805
  // All syntax surrounded by square brackets shouldn't contain line breaks.
6708
6806
 
6709
6807
 
6710
- exports.placeholder = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[:', '[^'], (0, combinator_1.surround)((0, source_1.str)(/^\[[:^]/), (0, combinator_1.syntax)(0
6711
- /* Syntax.none */
6808
+ exports.placeholder = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[:', '[^'], (0, combinator_1.surround)((0, source_1.str)(/^\[[:^]/), (0, combinator_1.syntax)(1024
6809
+ /* Syntax.placeholder */
6712
6810
  , 2, 1, 0
6713
6811
  /* State.none */
6714
6812
  , (0, visibility_1.startTight)((0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]]))), (0, source_1.str)(']'), false, ([as, bs], rest) => [[(0, dom_1.html)('span', {
@@ -6928,8 +7026,10 @@ const textlink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(8
6928
7026
  /* State.link */
6929
7027
  , false, (0, combinator_1.syntax)(256
6930
7028
  /* Syntax.link */
6931
- , 2, 10, 254
6932
- /* State.linkable */
7029
+ , 2, 10, 250
7030
+ /* State.linkers */
7031
+ | 4
7032
+ /* State.media */
6933
7033
  , (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ']', [[/^\\?\n/, 9], [']', 2]]), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])), ([params, content = []], rest, context) => {
6934
7034
  return parse(content, params, rest, context);
6935
7035
  }))));
@@ -6939,10 +7039,8 @@ const medialink = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(8
6939
7039
  /* State.media */
6940
7040
  , false, (0, combinator_1.syntax)(256
6941
7041
  /* Syntax.link */
6942
- , 2, 10, 254
6943
- /* State.linkable */
6944
- ^ 4
6945
- /* State.media */
7042
+ , 2, 10, 250
7043
+ /* State.linkers */
6946
7044
  , (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.sequence)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.union)([inline_1.media, inline_1.shortmedia]), ']')), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])), ([params, content = []], rest, context) => parse(content, params, rest, context)))));
6947
7045
  exports.unsafelink = (0, combinator_1.lazy)(() => (0, combinator_1.creation)(10, (0, combinator_1.precedence)(2, (0, combinator_1.bind)((0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([source_1.unescsource]), ']'), ']')), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])), ([params, content = []], rest, context) => parse(content, params, rest, context)))));
6948
7046
  exports.uri = (0, combinator_1.union)([(0, combinator_1.open)(/^[^\S\n]+/, (0, source_1.str)(/^\S+/)), (0, source_1.str)(/^[^\s{}]+/)]);
@@ -6958,7 +7056,7 @@ function parse(content, params, rest, context) {
6958
7056
  content = (0, dom_1.defrag)(content);
6959
7057
 
6960
7058
  for (let source = (0, util_1.stringify)(content); source;) {
6961
- if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:\/\/[^/?#]/i.test(source)) return;
7059
+ if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(source)) return;
6962
7060
  const result = autolink({
6963
7061
  source,
6964
7062
  context
@@ -7044,12 +7142,12 @@ exports.resolve = resolve;
7044
7142
 
7045
7143
  function decode(uri) {
7046
7144
  if (!uri.includes('%')) return uri;
7047
- const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:\/\/[^/?#]*/i)?.[0] ?? '';
7145
+ const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i)?.[0] ?? '';
7048
7146
 
7049
7147
  try {
7050
7148
  let path = (0, global_1.decodeURI)(uri.slice(origin.length));
7051
7149
 
7052
- if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:\/\/[^/?#]/i.test(path)) {
7150
+ if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(path)) {
7053
7151
  path = uri.slice(origin.length);
7054
7152
  }
7055
7153
 
@@ -7172,8 +7270,8 @@ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '
7172
7270
  /* State.media */
7173
7271
  , false, (0, combinator_1.syntax)(64
7174
7272
  /* Syntax.media */
7175
- , 2, 10, 0
7176
- /* State.none */
7273
+ , 2, 10, ~8
7274
+ /* State.link */
7177
7275
  , (0, combinator_1.bind)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([htmlentity_1.unsafehtmlentity, bracket, source_1.txt]), ']', [[/^\\?\n/, 9]]), ']', true)), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([link_1.uri, (0, combinator_1.some)(option)]), /^[^\S\n]*}/))]), ([as, bs]) => bs ? [[as.join('').trim() || as.join('')], bs] : [[''], as]), ([[text]]) => text === '' || text.trim() !== ''), ([[text], params], rest, context) => {
7178
7276
  const INSECURE_URI = params.shift();
7179
7277
  const url = new url_1.ReadonlyURL((0, link_1.resolve)(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href);
@@ -7195,7 +7293,7 @@ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['![', '
7195
7293
  if (context.state & 8
7196
7294
  /* State.link */
7197
7295
  ) return [[el], rest];
7198
- if (cache && cache.tagName !== 'IMG') return (0, combinator_1.creation)(10, (..._) => [[el], rest])({
7296
+ if (cache && cache.tagName !== 'IMG') return (0, combinator_1.creation)(10, _ => [[el], rest])({
7199
7297
  source: '!',
7200
7298
  context
7201
7299
  });
@@ -7279,7 +7377,7 @@ const dom_1 = __webpack_require__(3252);
7279
7377
 
7280
7378
  exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('[[', (0, combinator_1.constraint)(64
7281
7379
  /* State.reference */
7282
- , false, (0, combinator_1.syntax)(4096
7380
+ , false, (0, combinator_1.syntax)(8192
7283
7381
  /* Syntax.reference */
7284
7382
  , 6, 1, 128
7285
7383
  /* State.annotation */
@@ -7335,11 +7433,17 @@ const dom_1 = __webpack_require__(3252);
7335
7433
 
7336
7434
  const array_1 = __webpack_require__(8112);
7337
7435
 
7338
- exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[', (0, combinator_1.syntax)(0
7339
- /* Syntax.none */
7340
- , 2, 1, 0
7341
- /* State.none */
7342
- , (0, combinator_1.fmap)((0, combinator_1.verify)((0, combinator_1.sequence)([(0, combinator_1.surround)('[', (0, combinator_1.focus)(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=]\()/, text), ']'), (0, combinator_1.surround)('(', (0, combinator_1.focus)(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=\))/, text), ')')]), ([texts]) => (0, visibility_1.isStartTightNodes)(texts)), ([texts, rubies]) => {
7436
+ exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('[', (0, combinator_1.syntax)(512
7437
+ /* Syntax.ruby */
7438
+ , 2, 1, -1
7439
+ /* State.all */
7440
+ , (0, combinator_1.fmap)((0, combinator_1.verify)((0, combinator_1.fmap)((0, combinator_1.sequence)([(0, combinator_1.surround)('[', (0, source_1.str)(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ']'), (0, combinator_1.surround)('(', (0, source_1.str)(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ')')]), ([texts, rubies], _, context) => [(0, parser_1.eval)(text({
7441
+ source: texts,
7442
+ context
7443
+ }), [])[0] ?? '', (0, parser_1.eval)(text({
7444
+ source: rubies,
7445
+ context
7446
+ }), [])[0] ?? '']), ([texts, rubies]) => texts && rubies && (0, visibility_1.isStartTightNodes)(texts)), ([texts, rubies]) => {
7343
7447
  texts[texts.length - 1] === '' && texts.pop();
7344
7448
 
7345
7449
  switch (true) {
@@ -7499,8 +7603,8 @@ const array_1 = __webpack_require__(8112);
7499
7603
 
7500
7604
  exports.template = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('{{', (0, combinator_1.syntax)(0
7501
7605
  /* Syntax.none */
7502
- , 2, 1, 0
7503
- /* State.none */
7606
+ , 2, 1, -1
7607
+ /* State.all */
7504
7608
  , (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), '}')), '}}', true, ([, ns = []], rest) => [[(0, dom_1.html)('span', {
7505
7609
  class: 'template'
7506
7610
  }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
package/markdown.d.ts CHANGED
@@ -829,8 +829,8 @@ export namespace MarkdownParser {
829
829
  // [AB](a b)
830
830
  Inline<'ruby'>,
831
831
  Parser<HTMLElement, Context, [
832
- RubyParser.TextParser,
833
- RubyParser.TextParser,
832
+ SourceParser.StrParser,
833
+ SourceParser.StrParser,
834
834
  ]> {
835
835
  }
836
836
  export namespace RubyParser {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.260.3",
3
+ "version": "0.260.6",
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",
@@ -34,7 +34,7 @@
34
34
  "@types/mocha": "9.1.1",
35
35
  "@types/power-assert": "1.5.8",
36
36
  "@types/prismjs": "1.26.0",
37
- "@typescript-eslint/parser": "^5.30.7",
37
+ "@typescript-eslint/parser": "^5.31.0",
38
38
  "babel-loader": "^8.2.5",
39
39
  "babel-plugin-unassert": "^3.2.0",
40
40
  "concurrently": "^7.3.0",
@@ -49,13 +49,13 @@
49
49
  "karma-mocha": "^2.0.1",
50
50
  "karma-power-assert": "^1.0.0",
51
51
  "mocha": "^10.0.0",
52
- "npm-check-updates": "^15.3.4",
52
+ "npm-check-updates": "^16.0.3",
53
53
  "semver": "^7.3.7",
54
- "spica": "0.0.573",
54
+ "spica": "0.0.591",
55
55
  "ts-loader": "^9.3.1",
56
56
  "typed-dom": "^0.0.301",
57
57
  "typescript": "4.7.4",
58
- "webpack": "^5.73.0",
58
+ "webpack": "^5.74.0",
59
59
  "webpack-cli": "^4.10.0",
60
60
  "webpack-merge": "^5.8.0"
61
61
  },
@@ -1,4 +1,8 @@
1
1
  export class Memo {
2
+ constructor({ targets = ~0 } = {}) {
3
+ this.targets = targets;
4
+ }
5
+ public readonly targets: number;
2
6
  private readonly memory: Record<string, readonly [any[], number] | readonly []>[/* pos */] = [];
3
7
  public get length(): number {
4
8
  return this.memory.length;
@@ -8,7 +12,7 @@ export class Memo {
8
12
  syntax: number,
9
13
  state: number,
10
14
  ): readonly [any[], number] | readonly [] | undefined {
11
- //console.log('get', position + this.offset, syntax, state, this.memory[position + this.offset - 1]?.[`${syntax}:${state}`]);;
15
+ //console.log('get', position, syntax, state, this.memory[position - 1]?.[`${syntax}:${state}`]);;
12
16
  const cache = this.memory[position - 1]?.[`${syntax}:${state}`];
13
17
  return cache?.length === 2
14
18
  ? [cache[0].slice(), cache[1]]
@@ -26,13 +30,13 @@ export class Memo {
26
30
  record[`${syntax}:${state}`] = nodes
27
31
  ? [nodes.slice(), offset]
28
32
  : [];
29
- //console.log('set', position + this.offset, syntax, state, record[`${syntax}:${state}`]);
33
+ //console.log('set', position, syntax, state, record[`${syntax}:${state}`]);
30
34
  }
31
35
  public clear(position: number): void {
32
36
  const memory = this.memory;
33
37
  for (let i = position, len = memory.length; i < len; ++i) {
34
38
  memory.pop();
35
39
  }
36
- //console.log('clear', position + this.offset + 1);
40
+ //console.log('clear', position + 1);
37
41
  }
38
42
  }
@@ -5,12 +5,15 @@ import { Memo } from './context/memo';
5
5
 
6
6
  export function reset<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
7
7
  export function reset<T>(base: Ctx, parser: Parser<T>): Parser<T> {
8
+ if (!('memo' in base)) {
9
+ base.memo = undefined;
10
+ }
8
11
  assert(Object.getPrototypeOf(base) === Object.prototype);
9
12
  assert(Object.freeze(base));
10
13
  const changes = Object.entries(base);
11
14
  const values = Array(changes.length);
12
15
  return ({ source, context }) =>
13
- apply(parser, source, ObjectCreate(context), changes, values);
16
+ apply(parser, source, ObjectCreate(context), changes, values, true);
14
17
  }
15
18
 
16
19
  export function context<P extends Parser<unknown>>(base: Context<P>, parser: P): P;
@@ -23,18 +26,22 @@ export function context<T>(base: Ctx, parser: Parser<T>): Parser<T> {
23
26
  apply(parser, source, context, changes, values);
24
27
  }
25
28
 
26
- function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[]): Result<Tree<P>>;
27
- function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[]): Result<T> {
29
+ function apply<P extends Parser<unknown>>(parser: P, source: string, context: Context<P>, changes: [string, any][], values: any[], reset?: boolean): Result<Tree<P>>;
30
+ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [string, any][], values: any[], reset = false): Result<T> {
28
31
  if (context) for (let i = 0; i < changes.length; ++i) {
29
32
  const change = changes[i];
30
33
  const prop = change[0];
31
34
  switch (prop) {
32
35
  case 'resources':
36
+ if (!reset) break;
33
37
  assert(typeof change[1] === 'object');
34
38
  assert(context[prop] || !(prop in context));
35
39
  if (prop in context && !hasOwnProperty(context, prop)) break;
36
- // @ts-expect-error
37
- context[prop] = ObjectCreate(change[1]);
40
+ context[prop as string] = ObjectCreate(change[1]);
41
+ break;
42
+ case 'memo':
43
+ if (!reset) break;
44
+ context.memo = new Memo({ targets: context.memo?.targets });
38
45
  break;
39
46
  default:
40
47
  values[i] = context[prop];
@@ -47,7 +54,10 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
47
54
  const prop = change[0];
48
55
  switch (prop) {
49
56
  case 'resources':
50
- break;
57
+ // @ts-expect-error
58
+ case 'memo':
59
+ if (!reset) break;
60
+ // fallthrough
51
61
  default:
52
62
  context[prop] = values[i];
53
63
  values[i] = undefined;
@@ -61,26 +71,25 @@ export function syntax<T>(syntax: number, prec: number, cost: number, state: num
61
71
  return creation(cost, precedence(prec, ({ source, context }) => {
62
72
  if (source === '') return;
63
73
  const memo = context.memo ??= new Memo();
64
- context.memorable ??= ~0;
65
74
  context.offset ??= 0;
66
75
  const position = source.length + context.offset!;
67
- const st0 = context.state ?? 0;
68
- const st1 = context.state = st0 | state;
69
- const cache = syntax && memo.get(position, syntax, st1);
76
+ const stateOuter = context.state ?? 0;
77
+ const stateInner = context.state = stateOuter | state;
78
+ const cache = syntax && stateInner & memo.targets && memo.get(position, syntax, stateInner);
70
79
  const result: Result<T> = cache
71
80
  ? cache.length === 0
72
81
  ? undefined
73
82
  : [cache[0], source.slice(cache[1])]
74
83
  : parser!({ source, context });
75
- if (syntax && st0 & context.memorable!) {
76
- cache ?? memo.set(position, syntax, st1, eval(result), source.length - exec(result, '').length);
77
- assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, st1));
84
+ if (syntax && stateOuter & memo.targets) {
85
+ cache ?? memo.set(position, syntax, stateInner, eval(result), source.length - exec(result, '').length);
86
+ assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, stateInner));
78
87
  }
79
- if (result && !st0 && memo.length! >= position + 2) {
80
- assert(!(st0 & context.memorable!));
88
+ if (result && !stateOuter && memo.length! >= position + 2) {
89
+ assert(!(stateOuter & memo.targets));
81
90
  memo.clear(position + 2);
82
91
  }
83
- context.state = st0;
92
+ context.state = stateOuter;
84
93
  return result;
85
94
  }));
86
95
  }
@@ -20,7 +20,6 @@ export interface Ctx {
20
20
  precedence?: number;
21
21
  delimiters?: Delimiters;
22
22
  state?: number;
23
- memorable?: number;
24
23
  memo?: Memo;
25
24
  }
26
25
  export type Tree<P extends Parser<unknown>> = P extends Parser<infer T> ? T : never;
@@ -2,6 +2,7 @@ import { undefined, location } from 'spica/global';
2
2
  import { ParserSettings, Progress } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
5
+ import { Memo } from '../../combinator/data/parser/context/memo';
5
6
  import { segment, validate, MAX_INPUT_SIZE } from '../segment';
6
7
  import { header } from '../header';
7
8
  import { block } from '../block';
@@ -24,7 +25,7 @@ export function bind(target: DocumentFragment | HTMLElement | ShadowRoot, settin
24
25
  let context: MarkdownParser.Context = {
25
26
  ...settings,
26
27
  host: settings.host ?? new ReadonlyURL(location.pathname, location.origin),
27
- memorable: State.backtrackable,
28
+ memo: new Memo({ targets: State.backtrackers }),
28
29
  };
29
30
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
30
31
  assert(!settings.id);
@@ -2,6 +2,7 @@ import { location } from 'spica/global';
2
2
  import { ParserOptions } from '../../..';
3
3
  import { MarkdownParser } from '../../../markdown';
4
4
  import { eval } from '../../combinator/data/parser';
5
+ import { Memo } from '../../combinator/data/parser/context/memo';
5
6
  import { segment, validate, MAX_SEGMENT_SIZE } from '../segment';
6
7
  import { header } from '../header';
7
8
  import { block } from '../block';
@@ -30,7 +31,7 @@ export function parse(source: string, opts: Options = {}, context?: MarkdownPars
30
31
  ...context?.resources && {
31
32
  resources: context.resources,
32
33
  },
33
- memorable: State.backtrackable,
34
+ memo: new Memo({ targets: State.backtrackers }),
34
35
  };
35
36
  if (context.host?.origin === 'null') throw new Error(`Invalid host: ${context.host.href}`);
36
37
  const node = frag();
@@ -39,11 +39,16 @@ describe('Unit: parser/block/reply/cite', () => {
39
39
  assert.deepStrictEqual(inspect(parser('>>>0\n>>')), [['<span class="cite">&gt;&gt;<a class="anchor" href="?at=0" data-depth="2">&gt;0</a></span>', '<br>'], '>>']);
40
40
  assert.deepStrictEqual(inspect(parser('>>>0\n>>1')), [['<span class="cite">&gt;&gt;<a class="anchor" href="?at=0" data-depth="2">&gt;0</a></span>', '<br>', '<span class="cite">&gt;<a class="anchor" href="?at=1" data-depth="1">&gt;1</a></span>', '<br>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('>>.')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;.</a></span>', '<br>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('>>. ')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;.</a></span>', '<br>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('>>.\n')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;.</a></span>', '<br>'], '']);
42
44
  assert.deepStrictEqual(inspect(parser('>>#')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#</a></span>', '<br>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('>>#\n')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#</a></span>', '<br>'], '']);
43
46
  assert.deepStrictEqual(inspect(parser('>>#a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#a</a></span>', '<br>'], '']);
44
47
  assert.deepStrictEqual(inspect(parser('>>#index:a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#index:a</a></span>', '<br>'], '']);
45
48
  assert.deepStrictEqual(inspect(parser('>>#:~:text=a')), [['<span class="cite">&gt;<a class="anchor" data-depth="1">&gt;#:~:text=a</a></span>', '<br>'], '']);
46
49
  assert.deepStrictEqual(inspect(parser('>>http://host')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
50
+ assert.deepStrictEqual(inspect(parser('>>http://host ')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
51
+ assert.deepStrictEqual(inspect(parser('>>http://host\n')), [['<span class="cite">&gt;<a class="anchor" href="http://host" target="_blank" data-depth="1">&gt;http://host</a></span>', '<br>'], '']);
47
52
  assert.deepStrictEqual(inspect(parser('>>https://host')), [['<span class="cite">&gt;<a class="anchor" href="https://host" target="_blank" data-depth="1">&gt;https://host</a></span>', '<br>'], '']);
48
53
  });
49
54
 
@@ -7,14 +7,15 @@ import { html, define, defrag } from 'typed-dom/dom';
7
7
  export const cite: ReplyParser.CiteParser = creation(1, false, line(fmap(validate(
8
8
  '>>',
9
9
  reverse(tails([
10
- str(/^>*(?=>>[^>\s]+[^\S\n]*(?:$|\n))/),
10
+ str(/^>*(?=>>[^>\s]+\s*$)/),
11
11
  union([
12
12
  anchor,
13
13
  // Subject page representation.
14
14
  // リンクの実装は後で検討
15
- focus(/^>>\.[^\S\n]*(?:$|\n)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
- focus(/^>>#\S*[^\S\n]*(?:$|\n)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
17
- focus(/^>>https?:\/\/\w\S*[^\S\n]*(?:$|\n)/, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
15
+ focus(/^>>\.(?=\s*$)/, () => [[html('a', { class: 'anchor' }, '>>.')], '']),
16
+ focus(/^>>#\S*(?=\s*$)/, ({ source }) => [[html('a', { class: 'anchor' }, source)], '']),
17
+ // Support all domains, but don't support IP(v6) addresses.
18
+ focus(/^>>https?:\/\/[^\p{C}\p{S}\p{P}\s]\S*(?=\s*$)/u, ({ source }) => [[html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source)], '']),
18
19
  ]),
19
20
  ]))),
20
21
  ([el, quotes = '']: [HTMLElement, string?]) => [
@@ -1,8 +1,9 @@
1
1
  export const enum Syntax {
2
- reference = 1 << 12,
3
- comment = 1 << 11,
4
- index = 1 << 10,
5
- placeholder = 1 << 9,
2
+ reference = 1 << 13,
3
+ comment = 1 << 12,
4
+ index = 1 << 11,
5
+ placeholder = 1 << 10,
6
+ ruby = 1 << 9,
6
7
  link = 1 << 8,
7
8
  bracket = 1 << 7,
8
9
  media = 1 << 6,
@@ -25,15 +26,15 @@ export const enum State {
25
26
  autolink = 1 << 1,
26
27
  shortcut = 1 << 0,
27
28
  none = 0,
28
- linkable = 0
29
+ all = ~0,
30
+ linkers = 0
29
31
  | State.annotation
30
32
  | State.reference
31
33
  | State.index
32
34
  | State.label
33
35
  | State.link
34
- | State.media
35
36
  | State.autolink,
36
- backtrackable = 0
37
+ backtrackers = 0
37
38
  | State.annotation
38
39
  | State.reference
39
40
  | State.index
@@ -14,7 +14,7 @@ import { stringify } from '../util';
14
14
  export const autolink: AutolinkParser = fmap(
15
15
  validate(/^(?:[@#>0-9a-z]|\S[#>])/i,
16
16
  constraint(State.autolink, false,
17
- syntax(Syntax.autolink, 1, 1, State.none,
17
+ syntax(Syntax.autolink, 1, 1, ~State.shortcut,
18
18
  some(union([
19
19
  url,
20
20
  email,
@@ -13,7 +13,7 @@ import IndexParser = ExtensionParser.IndexParser;
13
13
  export const index: IndexParser = lazy(() => validate('[#', fmap(indexee(surround(
14
14
  '[#',
15
15
  constraint(State.index, false,
16
- syntax(Syntax.index, 2, 1, State.linkable,
16
+ syntax(Syntax.index, 2, 1, State.linkers | State.media,
17
17
  startTight(
18
18
  open(stropt(/^\|?/), trimBlankEnd(some(union([
19
19
  signature,
@@ -13,7 +13,7 @@ import { unshift } from 'spica/array';
13
13
 
14
14
  export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate(['[:', '[^'], surround(
15
15
  str(/^\[[:^]/),
16
- syntax(Syntax.none, 2, 1, State.none,
16
+ syntax(Syntax.placeholder, 2, 1, State.none,
17
17
  startTight(some(union([inline]), ']', [[/^\\?\n/, 9], [']', 2]]))),
18
18
  str(']'), false,
19
19
  ([as, bs], rest) => [[
@@ -29,6 +29,8 @@ describe('Unit: parser/inline/link', () => {
29
29
  assert.deepStrictEqual(inspect(parser('{http://a%C3%A1}')), [['<a class="url" href="http://a%C3%A1" target="_blank">http://a%C3%A1</a>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser('[http://á]{http://evil}')), undefined);
31
31
  assert.deepStrictEqual(inspect(parser('[xxx://á]{http://evil}')), undefined);
32
+ assert.deepStrictEqual(inspect(parser('[mailto:á]{http://evil}')), undefined);
33
+ assert.deepStrictEqual(inspect(parser('[file:///]{http://evil}')), undefined);
32
34
  assert.deepStrictEqual(inspect(parser('[.http://á]{http://evil}')), undefined);
33
35
  assert.deepStrictEqual(inspect(parser('[0987654321]{tel:1234567890}')), undefined);
34
36
  assert.deepStrictEqual(inspect(parser('[1234567890-]{tel:1234567890}')), undefined);
@@ -25,7 +25,7 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], union([
25
25
 
26
26
  const textlink: LinkParser.TextLinkParser = lazy(() =>
27
27
  constraint(State.link, false,
28
- syntax(Syntax.link, 2, 10, State.linkable,
28
+ syntax(Syntax.link, 2, 10, State.linkers | State.media,
29
29
  bind(reverse(tails([
30
30
  dup(surround(
31
31
  '[',
@@ -41,7 +41,7 @@ const textlink: LinkParser.TextLinkParser = lazy(() =>
41
41
 
42
42
  const medialink: LinkParser.MediaLinkParser = lazy(() =>
43
43
  constraint(State.link | State.media, false,
44
- syntax(Syntax.link, 2, 10, State.linkable ^ State.media,
44
+ syntax(Syntax.link, 2, 10, State.linkers,
45
45
  bind(reverse(sequence([
46
46
  dup(surround(
47
47
  '[',
@@ -88,7 +88,7 @@ function parse(
88
88
  if (content.length !== 0 && trimNode(content).length === 0) return;
89
89
  content = defrag(content);
90
90
  for (let source = stringify(content); source;) {
91
- if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:\/\/[^/?#]/i.test(source)) return;
91
+ if (/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(source)) return;
92
92
  const result = autolink({ source, context });
93
93
  if (typeof eval(result, [])[0] === 'object') return;
94
94
  source = exec(result, '');
@@ -202,10 +202,10 @@ export function resolve(uri: string, host: URL | Location, source: URL | Locatio
202
202
 
203
203
  function decode(uri: string): string {
204
204
  if (!uri.includes('%')) return uri;
205
- const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:\/\/[^/?#]*/i)?.[0] ?? '';
205
+ const origin = uri.match(/^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i)?.[0] ?? '';
206
206
  try {
207
207
  let path = decodeURI(uri.slice(origin.length));
208
- if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:\/\/[^/?#]/i.test(path)) {
208
+ if (!origin && /^[a-z](?:[-.](?=\w)|[0-9a-z])*:(?:\/{0,2}[^/?#\s]+|\/\/(?=[/]))/i.test(path)) {
209
209
  path = uri.slice(origin.length);
210
210
  }
211
211
  uri = origin + path;
@@ -21,7 +21,7 @@ Object.setPrototypeOf(optspec, null);
21
21
  export const media: MediaParser = lazy(() => validate(['![', '!{'], open(
22
22
  '!',
23
23
  constraint(State.media, false,
24
- syntax(Syntax.media, 2, 10, State.none,
24
+ syntax(Syntax.media, 2, 10, ~State.link,
25
25
  bind(verify(fmap(tails([
26
26
  dup(surround(
27
27
  '[',
@@ -55,7 +55,7 @@ export const media: MediaParser = lazy(() => validate(['![', '!{'], open(
55
55
  el.style.aspectRatio = el.getAttribute('aspect-ratio')!;
56
56
  }
57
57
  if (context.state! & State.link) return [[el], rest];
58
- if (cache && cache.tagName !== 'IMG') return creation(10, (..._) => [[el!], rest])({ source: '!', context });
58
+ if (cache && cache.tagName !== 'IMG') return creation(10, _ => [[el!], rest])({ source: '!', context });
59
59
  return fmap(
60
60
  unsafelink as MediaParser,
61
61
  ([link]) => [define(link, { class: null, target: '_blank' }, [el])])
@@ -1,20 +1,24 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { RubyParser } from '../inline';
3
3
  import { eval, exec } from '../../combinator/data/parser';
4
- import { sequence, syntax, creation, validate, verify, focus, surround, lazy, fmap } from '../../combinator';
4
+ import { sequence, syntax, creation, validate, verify, surround, lazy, fmap } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
- import { text as txt } from '../source';
6
+ import { text as txt, str } from '../source';
7
7
  import { Syntax, State } from '../context';
8
8
  import { isStartTightNodes } from '../visibility';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
  import { unshift, push } from 'spica/array';
11
11
 
12
- export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.none, 2, 1, State.none, fmap(verify(
12
+ export const ruby: RubyParser = lazy(() => validate('[', syntax(Syntax.ruby, 2, 1, State.all, fmap(verify(fmap(
13
13
  sequence([
14
- surround('[', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=]\()/, text), ']'),
15
- surround('(', focus(/^(?:\\[^\n]|[^\\[\](){}"\n])+(?=\))/, text), ')'),
14
+ surround('[', str(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ']'),
15
+ surround('(', str(/^(?:\\[^\n]|[^\\[\](){}"\n])+/), ')'),
16
16
  ]),
17
- ([texts]) => isStartTightNodes(texts)),
17
+ ([texts, rubies], _, context) => [
18
+ eval(text({ source: texts, context }), [])[0] ?? '',
19
+ eval(text({ source: rubies, context }), [])[0] ?? '',
20
+ ]),
21
+ ([texts, rubies]) => texts && rubies && isStartTightNodes(texts)),
18
22
  ([texts, rubies]) => {
19
23
  texts[texts.length - 1] === '' && texts.pop();
20
24
  switch (true) {
@@ -7,7 +7,7 @@ import { html } from 'typed-dom/dom';
7
7
  import { unshift } from 'spica/array';
8
8
 
9
9
  export const template: TemplateParser = lazy(() => surround(
10
- '{{', syntax(Syntax.none, 2, 1, State.none, some(union([bracket, escsource]), '}')), '}}', true,
10
+ '{{', syntax(Syntax.none, 2, 1, State.all, some(union([bracket, escsource]), '}')), '}}', true,
11
11
  ([, ns = []], rest) => [[html('span', { class: 'template' }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
12
12
 
13
13
  const bracket: TemplateParser.BracketParser = lazy(() => creation(union([