-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathes13.js
3540 lines (3277 loc) · 131 KB
/
es13.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'use strict'; // this is for ES13 aka ES2022
// In future: sed -i 's/__\([a-zA-Z]\)/#\1/g'
// Do not try to access __ things, as IT WILL FAIL in future!
// TODO:
//
// - Allow this to be loaded as Module, too.
// - Better Worker support.
//
// Proper debug sealing. Wrong usage must be reported via throw.
//
// Object.seal() and Object.freeze() fail for debugging,
// as they silently protect the object or class.
// But I want prominent, best catastrophic errors to show up.
//
// Hence we need to use Proxy()s while debugging,
// replacing them with normal Object.freeze() or Object.seal()
// in production (as this speeds things up in most browers).
//
// WeakMap possibly does not work on DOM objects at all.
//
// WTF? How to implement this properly then?
// I thought it was meant for exactly that case!
//
// Implement private fields (.. perhaps ..)
//
// Currently stupidly emulated with __
// Can we use WeakMap()[this] instead? Easily?
// (I did so in some other universe already.)
// This Works is placed under the terms of the Copyright Less License,
// see file COPYRIGHT.CLL. USE AT OWN RISK, ABSOLUTELY NO WARRANTY.
// Rules for members in classes (NOT on toplevel functions):
// Uppercase or CamelCase which starts uppercase return "async function" or "Promise".
// All lowercase returns "this". Always.
// Starting with $ are getter/setter with just $ is what you expect most (the wrapped object, etc.)
// mixedCaps or ALLCAPS or functions with _ in the name or ending on it return anything.
// Starting with _ is private, the first _ is skipped to apply the rules above.
//
// If something takes a function as argument, the argument list usually ends on (fn, ...args):
// - This then calls the function as fn(e, ...args)
// - If name ends on $ the call becomes fn.call(e, ...args)
// - If name ends on $$ it becomes (fn, args) and fn.apply(e, arg)
// <script src="es13.js" data-debug></script>
// data-debug enables debugging
try { // Else workers die if you try to access 'document', which is plain bullshit.
/* */ var DEBUGGING = this?.DEBUGGING || document?.currentScript?.dataset?.debug; // you can change this later
} catch {};
// Following allows to test syntax with NodeJS (we are not compatible to NodeJS as this is very DOM centric): js es13.js
// BrowserCompat(replacement,({window, document, ErrorEvent}) => { return .. }) // calls function with replacement-object when window etc. not available
// BrowserCompat(false, fn) // returns a throwing function if not in a browser
function BrowserCompat(o, _)
{
try {
return _({window, document, ErrorEvent}); // throws when these 3 are not available
} catch (e) {
// console.error(e);
return o === false ? (() => { THROW('needs a browser') }) : _(o); // replacement in case above throws
}
}
const knownNameSpaces =
{ "http://www.w3.org/1999/xhtml":[] // mainly used for this
, "http://www.w3.org/2000/svg":[] // incomplete
, "http://www.w3.org/1998/Math/MathML":void 0 // not handled for now
};
/* */ const DispatchEvent = async e => await window.dispatchEvent(e); // do it asynchronously to not stop execution
/* */ const _FPA = Function.prototype.apply; // _FPA.call(fn,THIS,[args..])
/*_*/ const _FPC = Function.prototype.call; // _FPC.call(fn,THIS,args..)
/*_*/ const DONOTHING = function(){} // The "do nothing" DUMMY function
/* */ const DEPRECATED = (_ => (...a) => { if (_) CONSOLE([_--].concat(a), new Error(`deprecation warning`)) })(100); // prints 100 occurances (hopefully with stacktrace)
/*_*/ const CONSOLE = this.CONSOLE || ((...a) => { console.log(...a) }); // returns void 0 for sure (and changeable)
// mostly sorted by ABC, //^v NAME if NAME is displaced (^v is direction)
/* */ const AsyncFun = Object.getPrototypeOf(async function(){}).constructor;
/* */ const C = (fn,...a) => function (...b) { return _FPC.call(fn,this,...a,...b) } // Curry (allows to bind this)
/* */ const C$ = (fn,self,...a) => C$$(fn,self,a); // Curry call (with self)
/* */ const C$$ = (fn,self,a) => (...b) => _FPC.call(fn,self,...a,...b); // Curry apply (with self)
/* */ const CA = C$$, CC = C$; // deprecated
// Report some error, but do not terminate execution (just returning VOID)
/* */ const CATCH = function(fn,...a) { return CATCH$$(fn,this,a) } // class { CATCH=CATCH } and then: this.CATCH(this.fn, args..)
/* */ const CATCH$ = (fn,self,...a) => CATCH$$(fn,self,a) // this.fn(args..) becomes CATCH$(this.fn, this, args..)
/* */ const CATCH$$ = (fn,self,a) => { try { return _FPC.call(fn,self,...a) } catch (e) { DispatchEvent(new ErrorEvent('esXX_catched_error_event', {error:e})) } }
//^ CONSOLE
/* */ //const CT = (fn,...a) => CA(fn,this,a) // instead use: C(this.fn,a) or CC(fn,this)
/* */ const D = (...a) => DEBUGGING ? CONSOLE('DEBUG', ...a) : void 0;
/* */ const DD = (...a) => DEBUGGING ? C(D,...a) : DONOTHING // log = DD('err in xxx'); log('whatever')
/* */ // mapSet(map,k,v) map.set(k,v) which returns the value
/* */ // mapDef(map,k,fn,args..) returns map[k], initialized with fn(args..) if not known already
/* */ const mapSet = (map,k,v) => (map.set(k,v), v)
/* */ const mapDef = (map,k,fill,...a) => map.has(k) ? map.get(k) : mapSet(map, k, fill(...a));
//v defArr
//^ DEPRECATED
//v DomReady
//^ DONOTHING
//v E
//v Fetch FetchProgress fetchProgress
//v fromJ
//v Get GetJSON GetText
// //
// for more easy compare: GetKeyCode(event) => {k,m,s} (plus uppercase flags from m/s)
// m: Shift Control Alt Meta
// s: capsLock Numlock sRolllock altGraph // future: conTextmenu
// Future: .S represents the detected key number (left:1, right:2, and so on)
// But without our own global generic keyboard processor this is not supported by browsers today.
// This sadly also includes things like ContextMenu (X).
const GetKeyCode = e =>
{
const o = { k:e.code };
var m='';
if (e.shiftKey) { o.S=-1; m += 'S' }
if (e.ctrlKey) { o.C=-1; m += 'C' }
if (e.altKey) { o.A=-1; m += 'A' }
if (e.metaKey) { o.M=-1; m += 'M' }
o.m = m;
var s='';
if (e.getModifierState('CapsLock')) { o.L=-1; s += 'L' }
if (e.getModifierState('NumLock')) { o.N=-1; s += 'N' }
if (e.getModifierState('ScrollLock')) { o.R=-1; s += 'R' }
if (e.getModifierState('AltGraph')) { o.G=-1; s += 'G' }
o.s = s;
return o;
}
// //e
//v IGN
/* */ const isArray = Array.isArray;
/* */ const isFunction = f => typeof f === 'function'; // https://stackoverflow.com/a/6000009
/* */ const isInt = i => Number.isInteger(i);
/* */ const isObject = o => typeof o === 'object' && (Object.getPrototypeOf(o || 0) || Object.prototype) === Object.prototype; // fixed. Following fails for OB(): https://stackoverflow.com/posts/comments/52802545
/* */ const isObjectOrNull = o => isObject(o ?? {}); // OB() or {} or null or void 0, but not 0 nor '' nor false nor Array
/* */ const isObjectOrFalse = o => isObject(o || {}); // OB() or {} or null or void 0 or 0 or '' or false, but not Array
/* */ const isString = s => s?.constructor === String; // https://stackoverflow.com/a/63945948
// Relative speeds tested with Chrome 95 in percent:
// 'str' 1 (new String)
// 100 100 100 s?.constructor === String
// 100 100 99 (typeof x == 'string') || (x instanceof String)
// 12 13 11 Object.prototype.toString.call(x) === "[object String]"
/* */ const mkArr = x => Array.isArray(x) ? x : [x]; // creates single element array from non-Array datatypes
/* */ const defArr = (x,d) => { x=mkArr(x); return x.length ? x : mkArr(d) } // same as mkArr, except for [] which becomes default array
// //
// Nonrecursive Array flattening, similar to [].flat(Infinity)
// (I am not convinced, that Array.flat() is nonrecursive!)
//
// ...[..] and recursions are limited, hence we cannot use them.
// for (x of [..]) must visit all [..] to avoid recursion.
// We cannot use .shift() or similar on original arrays.
//
// This has O(n) space complexity and O(n) time complexity:
// - It accesses each element FOUR times, hence O(n):
// - FIRST to copy it into a local array to operate on
// - SECOND to shift it out to the local array
// - THIRD to test it for the type
// - FOURTH to output the element (if it is not an Array)
// - It iterates on empty arrays or single elements, hence O(ld N)
// - Only arrays of 2 or more elements do a push onto stack
// - So we have a maximum of n/2 pushes, which is O(n)
// - However "foreign" (orig) arrays need to be copied to process them
// - So in the "already flat" case we have O(n) space need here
function* flattenArray(...a)
{
for (const stack=a; stack.length; )
for (let our = stack.pop(); our.length; )
// stack has only our Arrays, so we can use .shift()
for (let orig = our.shift();; orig = orig[0])
{
if (!isArray(orig)) // orig can be anything, void 0, null, {}, [], etc.
yield orig; // output non-Array element and iterate to next
else if (orig.length) // nonempty array?
{
if (orig.length===1) // single element case optimization
continue; // loop to orig[0]
if (our.length) // recursion needed?
stack.push(our) // push what is to do later onto our stack
our = Array.from(orig); // iterate on a copy of orig, so we can use .shift()
}
break; // next iteration
}
}
// We can avoid the copying by using the original array with an index
// (However index access might be slower than .shift())
function* flattenArrayI(...a)
{
for (const stack=[[a,0]]; stack.length; )
for (let [arr,i] = stack.pop(); i<arr.length; )
for (let orig = arr[i++];; orig = orig[0])
{
if (!isArray(orig)) // orig can be anything, void 0, null, {}, [], etc.
yield orig; // output non-Array element and iterate to next
else if (orig.length) // nonempty array?
{
if (orig.length===1) // single element case optimization
continue; // loop to orig[0]
if (i<arr.length) // recursion needed?
stack.push([arr,i]) // push what is to do later onto our stack
arr = orig; // iterate over orig
i = 0;
}
break; // next iteration
}
}
// //e
// //
// [a,b,c] => {}[fold(a)] = [a]
// also ignores nullish arguments
function FoldArraysIntoObject(fold, ...arrays)
{
const o = {};
for (const a of arrays)
a?.forEach((v,..._) => { const k = fold.call(this,v,..._); (o[k] || (o[k]=[])).push(v) });
return o;
}
// //e
/* */ // I hate this. Why is debugging Promises so hard? Why isn't it built in?
/* */ // Promise.resolve().then(_ => randomly_failing_function()).then(OK).catch(KO).then(...OKO('mypromise'))
/* */ const KO = (e, ...a) => { D('catch', e, ...a); throw e } // Promise.reject().catch(KO).then(not_executed)
/* */ const OB = (...a) => Object.assign(Object.create(null), ...a); // Plain Object without protoype (just O considered too error prone)
// //
const OBfix = o =>
{
let r;
if (isArray(o))
r = [];
else if (isObject(o))
r = OB();
else
return o;
for (const i in o)
r[i] = OBfix(o[i]);
return r;
};
// //e
/* */ const OK = (v, ...a) => { D('then', v, ...a); return v } // Promise.resolve().then(OK).then(..)
/* */ const OKO = (...a) => [ v => OK(v, ...a), e => KO(e, ...a) ] // Promise.reject.then(...OKO('mypromise')).then(not_executed)
/* */ const KOK = (...a) => DD(...a) // Promise.reject().catch(KOK('shown when fail&debug')).then(..)
/* */ const IGN = (...a) => (...b) => CONSOLE(...a, ...b) // Promise.reject().catch(IGN('error is ignored')).then(..)
// Create real Error()s on catch chains for better processing.
//
// https://developer.mozilla.org/en-US/docs/Web/API/Error
// Error() is NOT standardized, however .stack and .lineNumber etc. are supported in most browsers.
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
// ErrorEvent() IS standardized, but does NOT contain .stack and it is barely documented.
//
// You can only rely on:
// .message
// .stack
// everything else is too browser specific
//
// Promise.reject('throw').catch(THROW).catch(e => bug(e.message, e.stack))
/* */ const THROW = BrowserCompat({ErrorEvent:Event}, ({ErrorEvent}) => e => { e = e instanceof Error ? e : e instanceof ErrorEvent ? new Error(e.message, e.filename, e.lineno, e.colno) : new Error(e); D('ERROR', e); throw e });
// P(fn, args) is short for: new Promise((ok,ko) => { try { ok(fn(args)) } catch (e) { ko(e) })
/* */ const PO = () => { const o={}; o.p = new Promise((ok,ko) => { o.ok=ok; o.ko=ko }); return o } // PromiseObject
/* */ const POC = () => { const o = PO(); o.p.catch(IGN); return o } // PromoseObject with default catch
/* */ const PR = Promise.resolve(); // PRomise
/* */ const PE = Promise.reject(); // PromisErr WARNING: Only use PE instead of Promise.reject() if you want to suppress the "Uncaught in Promise" error by default!
/* */ PE.catch(DONOTHING); // shutup "Uncaught in Promise" due to PE
/* */ // Tell error without sideeffects
/* */ // try{..}catch(e){PErr(e)}
/* */ // P.then(..).catch(PErr);
/* */ const PErr = e=>{Promise.reject(e).catch(THROW)}; // unfortunately this does not give us the real error position, only where it is handled, but this is better than nothing
/* */ const P = (fn,...a) => PR.then(() => fn(...a)); // invoke fn(a) in microtask: P(fn,a).then(..).catch(..). See also single_run()
/* */ const P$ = (fn,self,...a) => P$$(fn,self,a); // invoke fn(a) with this===self
/* */ const P$$ = (fn,self,a) => PR.then(() => _FPA.call(fn, self, a)); // same as P$ but with arguments in array (for optimization)
//v PostJSON PostText
//v PutJSON PutText
/* */ const PC = P$; // deprecated
// //
// Promise(s) with timeouts
// Resolve all given Promises within the given time
// If one rejects (or timeout) this rejects.
const Ptimeout = (ms, ...prom) =>
{
if (prom.length != 1) prom = [Promise.all(prom)];
const reject = SleEp(ms, 'timeout');
reject.catch(DONOTHING);
prom.push(reject)
return Promise.race(prom); // Reject after timeout
};
// //e
/* */ const DomReady = BrowserCompat( {}, ({document}) => document ? new Promise(ok => document.readyState==='loading' ? document.addEventListener('DOMContentLoaded', ok) : ok()) : PE );
/* */ const fromJ = s => OBfix(JSON.parse(s)); // false === 'constructor' in fromJ('{}')
/* */ const toJ = o => JSON.stringify(o);
/* */ const sortJ = (ob,sort,space) => JSON.stringify(ob // sorted JSON.stringify, see https://stackoverflow.com/a/43636793
/* */ , (k,v) =>
/* */ (v instanceof Object && !(v instanceof Array || v instanceof Date || v instanceof Function))
/* */ ? Object.keys(v).sort(sort).reduce((x,y) => (x[y] = v[y], x), {})
/* */ : v
/* */ , space);
/* */ const SleeP = (ms,v) => new Promise(ok => setTimeout(ok, ms, v)); // await SleeP(10).then(..)
/* */ const SleEp = (ms,e) => new Promise((ok,ko) => setTimeout(ko, ms, e)); // await SleEp(10).catch(..)
/* */ const sleepFn = ms => r => SleeP(ms, r); // .then(sleepFn(10)).then(..)
/* */ const sleepErr = ms => e => SleEp(ms, e); // .catch(sleepErr(10)).catch(..)
// fetch() promises
// p is _ => fetchProgress(_, ..) from below, use like:
// Get(URL, _ => fetchProgress(_, fn, args..))
/* */ const Fetch = (u,o,p) => fetch(u,o).then(p || (_=>_)).then(r => r.ok ? r : Promise.reject(`${r.status}: ${r.url}`))
/* */ const Get = (u,p) => Fetch(u, { cache:'no-cache' }, p)
/* */ const GetC = (u,p) => Fetch(u, { cache:'no-cache', credentials:'include' }, p)
/* */ const _MTFUD = (m,t,f) => (u,d,p) => Fetch(u, { cache:'no-cache', method:m, headers:{'Content-Type':t}, body:f ? f(d) : d }, p)
/* */ const PostText = _MTFUD('POST', 'text/plain')
/* */ const PutText = _MTFUD('PUT', 'text/plain')
/* */ const _MUJ = m => _MTFUD(m, 'application/json', JSON.stringify)
/* */ const PostJSON = _MUJ('POST')
/* */ const PutJSON = _MUJ('PUT')
/* */ const _Json = p => p.then(r => r.status==200 ? r.json() : THROW(r.status))
/* */ const _Text = p => p.then(r => r.status==200 ? r.text() : THROW(r.status))
/* */ const GetText = (u,p) => _Text(Get(u,p))
/* */ const GetTextC = (u,p) => _Text(GetC(u,p))
/* */ const GetJSON = (u,p) => _Json(Get(u,p))
// Why isn't something similar already in the spec as an option?
//
// Fetch('url').then( fetch_progress (callback, ...args)).then(_ => _.text()) // syntactic Sugar
// Fetch('url').then(_ => FetchProgress(_, callback, ...args)).then(_ => _.text())
//
// Calls callback(...args, pos, total, original_response) with this==original_response
// total is void 0 (AKA: undefined) if Content-Length header missing
// original-response is the _ above, also passed as this
//
// Why is it so complex to make it somewhat efficient?
// Following looks a bit too much like Java for my taste .. sorry:
// https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream
//F//
const fetch_progress = (...a) => _ => FetchProgress(_, ...a)
const FetchProgress = (_, fn, ...args) =>
{
const reader = _.body.getReader();
const cl = _.headers.get('content-length');
const total = cl === void 0 ? cl : (cl|0);
let pos = 0;
const start = controller =>
{
return pump();
function pump()
{
_FPC.call(fn, _, ...args, pos, total, _);
return reader.read().then(chunk =>
{
if (chunk.done)
{
controller.close();
return;
}
pos += chunk.value.length;
controller.enqueue(chunk.value); // this can be quite big, right?
return pump();
});
}
};
return new Response(new ReadableStream({start}), _);
};
//F//e
//^ THROW
// Escape URI and (only the problematic) HTML entities
// As there are gazillions of named HTML entities (and counting)
// we do NOT want to support them. Never. Sorry.
const UE = x => encodeURIComponent(x); // URLencoded WTF? I almost broke a finger typing this!
const UD = x => decodeURIComponent(x); // URLdecoded WTF? BTW: Out of 666 characters long names?
const HE = x => String(x).replace(/[&<>"]/g, c => `&#${c.charCodeAt(0)};`); // HTMLencoded
const HD = x => String(x).replace(/&#(\d)+;/g, (s,c) => String.fromCharChode(c)); // HTMLdecoded
const HU = x => HE(UE(x)); // HTML+URLencoded special short form (for inclusion in literal DOM)
const JU = x => UE(toJ(x)); // JSON URLencoded special short form (for inclusion in URL)
const JHU = x => HU(toJ(x)); // JSON HTML+URLencoded special short form (for inclusion in literal DOM)
const decodeHTML = _ => { const t=document.createElement('textarea'); t.innerHTML = _; return t.value }
const encodeHTML = _ => { const t=document.createElement('textarea'); t.innerText = _; return t.innerHTML.replace('"', '"') }
const strsplice = (s,from,to,replace) => `${s.substring(0,from)}${replace}${s.substring(to)}`;
// choices([1,2,3]) => [[1],[2],[1,2],[3],[2,3],[1,3],[1,2,3]]
// choices([1,2,3],2) => [[1,2],[1,3],[2,3],[1,2,3]]
// choices([1,2,3],2,2) => [[1,2],[1,3],[2,3]]
// with generators min and max are probably needed (as we do not know how long the generator is)
// Array.from(choices(...)) to get it as Array
const choices = function*(gen,min,max)
{
min |= 0;
max |= 0;
if (max && max < min) max = min;
max--;
min--;
const was = [];
for (const x of gen)
{
if (was.length >= min)
for (const t of choice(was, []))
yield t.concat([x]);
was.push(x);
}
function* choice(arr, r, ign)
{
if (r.length >= min && !ign)
yield r;
if ((max<0 || r.length < max) && arr.length)
{
const a = arr.slice();
const e = a.shift();
yield* choice(a, r.slice(), 1);
r.push(e);
yield* choice(a, r.slice());
}
}
}
// perms([1,2,3]) => [[1,2,3],[1,3,2],[3,1,2],[2,1,3],[2,3,1],[3,2,1]]
// perms([1,2],1) => [[1],[1,2],[2],[2,1]]
// perms([1,2],0) => [[1],[1,2],[2],[2,1]]
// with generators min and max are probably needed (as we do not know how long the generator is)
// Array.from(perms(...)) to get it as Array
const perms = function*(gen,min,max)
{
min ??= gen.length;
min |= 0;
max |= 0;
if (max && max < min) max = min;
min--;
max--;
const was = [];
for (const x of gen)
{
if (was.length >= min)
for (const t of perm(was, []))
for (let i=max>=0 && max<t.length ? max : t.length; i>=0; i--)
{
const r = t.slice();
r.splice(i,0,x);
yield r;
}
was.push(x);
}
function* perm(arr, r)
{
if (r.length >= min)
yield r;
if (max<0 || r.length < max)
for (let i=0; i<arr.length; i++)
{
const a = arr.slice();
yield* perm(a, r.concat(a.splice(i, 1)));
}
}
}
// alts([[a,b],[c,d]]) => [[a,c],[a,d],[b,c],[b,d]]
// alts([[[1,2],3],[a,[b,c]]]) => [[1,2,a],[1,2,b,c],[3,a],[3,b,c]]
// first argument can be Array or generator
// Array.from(alts(...)) to get it as Array
const alts = function*(gen)
{
const had = {};
for (const x of gen)
yield* alt(x, []);
function* alt(arr, r)
{
while (arr.length)
{
const e = arr.shift();
if (Array.isArray(e))
{
for (const x of e)
yield* alt(arr.slice(), r.concat(x)); // not .concat([x]), so array elements can insert more than 1 element
return;
}
r.push(e);
}
yield r;
}
}
// (ES11->ES13: I am not sure if and how Babel handles this correctly, so I keep it as-is for now.)
// Dummy support for perhaps missing WeakRefs. (This is mainly for Babel)
// The fallback is not good as it leaks memory, but we cannot implement this any better way here.
const es11WeakRef = (() =>
{
try {
new WeakRef({});
CONSOLE('es11WeakRef supported');
return WeakRef;
} catch {
CONSOLE('es11WeakRefs faked');
// Not a working WeakRef mixin
// (This cannot be implemented with WeakMap)
return class
{
__o
constructor(o) { this.__o = o }
deref() { return this.__o }
}
}
})();
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// OVERVIEW: (sync:immediately, async:microtask, cycle:task queue, frame:animation frame)
//
// r=tmpcache(fn,a..): sync: r(b) caches fn(a), but only in this cycle
// r=single_run(fn,a..): async: r(b) runs fn(a,b) if it not already runs, else re-runs last invocation when fn(a,b) finishes
// r=once_per_cycle(fn,a..): cycle: r(b) runs fn(a,b) once on end of cycle. r(b) returns unused arguments (previous invocation not realized)
// r=once_per_frame(fn,a..): frame: as once_per_cycle() but on animation frame.
// r=once_per_ms(ms,fn,a..): delay: as once_per_cycle() but last invocation after given ms
// note: once_per_cycle(fn,a..) is the same as once_per_ms(0,fn,a..)
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Temporarily cache something expensive (expires at the next loop)
// This is mainly for class methods like getters as it distinguishes on 'this'.
// However it should work with global function, too, as here 'this' is window.
const tmpcache = (fn,...a) => // use a real function here to provide 'this'
{
let ret;
return function () // we cannot have args here, as this would change the outcome
{ // must be function as we need 'this' for caching
if (ret)
{
if (ret.has(this))
return ret.get(this); // cached value
}
else
{
ret = new WeakMap(); // temporary cache
setTimeout(() => ret = void 0); // expire at next loop
}
ret.set(this, void 0); // avoid recursion
const v = fn.apply(this, a);
ret.set(this, v); // cache real value (from this cycle)
return v;
}
};
// gather function calls to execute at the end of the current cycle.
// function calls are remembered based on the first argument only.
// This differentiates by 'this', so can initialize methods.
// class X { thing = gather(function (args) { ... }) };
// x = new X();
// x.thing('a');
// x.thing('b');
// x.thing('a');
// // on the next cycle fn('a') and fn('b') are called, nothing else
const gather = fn =>
{
let map;
const run = () =>
{
const r = map;
map = void 0;
r.forEach((_,t) => _.forEach(a => fn.apply(t,a))); // crash early on bugs
};
return function (...a)
{
if (!map)
{
map = new Map();
setTimeout(run); // run after this cycle
}
mapDef(map, this, () => new Map()).set(a[0], a); // remember last added key only
}
};
class Cancelled
{
__a
constructor(...a) { this.__a = a }
get $cancelled() { return this.__a }
get cancelled() { DEPRECATED('please use .$cancelled'); return this.__a }
};
// Run something a single time in backround where only the last invocation is cached.
// Usage is to update something expensive, such that if multiple updates come in while it is updated, those are skipped except of the latest one.
//
// single_run(fn,a) returns a function which, when invoked, starts the given fn asynchronously with the given args, such that it never runs twice.
// If the fn still runs further invocations are cached and run afterwards. But only the last invocation survives, all othere throw a Cacnelled exception.
//
// Example:
//
// r = single_run(fn, a);
// r(b).then(
// val => process(val)
// ,
// err => err.$cancelled ? was_cancelled(err) : other_error(err)
// )
//
// - single_run(fn,a) returns a function (here called r)
// In future this might change to a callable class which encapsulates it all.
// - r(b) returns a Promise which resolves to the return value (or error) of fn(a,b)
// - Until this Promise is resolved, further calls to r(x) are delayed until the Promise resolves
// - If another r(y) arrives while r(x) is waiting, r(x) is rejected with the Cancelled() class (which does not derive from Error by purpose).
// r(y) replaces r(x) this way
// - On Cancelled() the .$cancelled property returns the (truthy) array of the given arguments which replaced the r(x) (the [a,y])
// Hence you can test with something like .catch(e => { if (e.$cancelled) ..
// This also works with try { await r(y) } catch (e) { if (e.$cancelled) ..
// - (single_run(fn,a..).cancelfn(cfn,c..))(b..) is the same as single_run(fn,a..)(b..), but sets cfn as function on cancel.
// If cfn === false, it mutes/disables/ignores Cancelled exceptions and returns b[0] (undefined by default).
// If cfn is not given (falsish), the newly created Promise (for the new call) is returned (to the old call), so this follows/waits for the new call.
// If cfn === true, the original behavior is restored.
// Else cfn is invoked as cfn(...c,a,b) on cancel.
// Rationale: If there is a function, give it.
// cancelfn(true) means "yes, do the cancel thing"
// cancelfn(false) kicks the cancel thing, returning void 0
// cancelfn(false, 1) returns the given value (you need to write something before the value)
// cancelfn() does not cancel, instead, it returns the latest promise
const single_run = (fn, ...a) =>
{
let invoke, running;
const run = async (...a) =>
{
//D('SR', 'wait', a);
await void 0; // run asynchrounously
//D('SR', 'run', a);
return fn(...a); // in case it is a Promise
};
const loop = () =>
{
running = invoke;
invoke = void 0;
//D('SR', 'loop', running)
if (running)
run(...running[0], ...running[1]).then(running[2], running[3]).finally(loop)
}
const def = (was,a,b) => was[3](new Cancelled(a,b));
let cancel = def;
const r = (...b) =>
{
const was = invoke;
const p = new Promise((ok, ko) =>
{
//D('SR', 'exec', fn, a, invoke);
invoke = [a,b,ok,ko];
if (!running)
loop();
});
if (was)
cancel(was,a,b,p);
return p;
};
function cancelfn(fn,...c)
{
cancel = fn
? fn===true ? def : (was,a,b) => was[2](P(fn,...c,a,b))
: fn===false ? was => was[2](c[0]) : (was,a,b,p) => was[2](p);
return this;
}
r.cancelfn = cancelfn;
return r;
};
// Wrap a functioncall such, that it is only called once in a cycle or frame:
// (For synchronous functions only. For ASYNC functions see single_run)
//
// const doitonce = once_per_cycle(console.log)
// doitonce(1); // returns void 0
// doitonce(2,3,4); // returns [1]
// doitonce('x'); // returns [2,3,4]
// await relax(); // console.log('x') is executed
//
// Only the last parameters from the call survive. Previous parameters are returned by the wrapper.
//
// If the executed function recursively calles the wrapper, this is ignored, so x=void 0 is at the correct place.
// If a once_per_cycle() function calles a new once_per_cycle(), it is executed immediately, but only once.
// Hence there cannot be an endless loop caused by this, each routine only is called once per cycle, at maximum.
const _run_once = _ => (f => f(_))(later =>
{
let run, block;
const exec = () =>
{
block = []; // create unique semaphore
try {
let x;
while (x=run.pop())
x();
} finally {
run = void 0;
block = void 0; // drop used semaphore
}
}
return (fn,...a) =>
{
let x, done = []; // no previous semaphore
const call = () => { if (block!==done && x) { done=block; const t=x; x=void 0; fn(...a,...t) } }; // run if semaphore changed
if (block)
throw new Exception('_run_once() called from within function executing once per cycle');
return (...b) =>
{
const was = x;
x = b;
if (block===done)
return b; // we return the args, which are not used (already used: was)
if (block)
{
call(); // we are within once-per-cycle, so run it immediately
return was; // we return the args, which are not used (just used: a)
}
// we are outside once-per-cycle here
if (was) return was; // we return the args, which are not used (will use: a)
if (!run)
{
later(exec); // process everyting once after this cycle (and Microtasks)
run = [];
}
run.push(call); // build list what to do in run[]
}
}
});
const once_per_cycle = _run_once(_ => setTimeout(_));
const once_per_frame = _run_once(_ => window.requestAnimationFrame(_));
const once_per_ms = (ms,...a) => _run_once(_ => setTimeout(_,ms))(...a);
const once_per_ms$ = (fn,...a) => _run_once(_ => setTimeout(_,fn(...a)))(...a);
// *UNTESTED*
// Revocable Promise: // at least what I came up with
// r = R(fn, ...args); // fn(r,...args) is called and should return a Promise
// .signal // AbortController.signal
// .abort // AbortController.abort (result is that of what the function returns due to the abort)
// .aborted // AbortController.signal.aborted
// .settled // true if Promise has resolved
// .onabort // AbortController.signal.onabort
// .on(fn, ...args) // AbortController.signal.addEventListener(() => fn(...args)). Returns function which, wenn called, removes the listener
// .revoke(cause) // AbortController.abort() plus reject Promise with given cause instead.
// .then() .catch() .finally() // same as for Promise
// .ok(_) // accept Promise, WITHOUT revocation
// .ko(_) // reject Promise, WITHOUT revocation
// Syntactic sugar: // .revoke=cause, .ok=_, .ko=_
class Revocable extends Promise
{
constructor(fn,...a)
{
const __ = { a:new AbortController() };
const sig = __.a.signal;
super((ok,ko) => { __.o = ok; __.k = ko });
const kick = x => _ => { if (__) { __ = void 0; x(_) } };
const ok = _ => kick(__.o)(_);
const ko = _ => kick(__.k)(_);
(async () => fn(this,...a))().then(kick(__.o), kick(__.k));
Object.defineProperty(this, 'ok', { get:() => _ => ok(_), set: ok });
Object.defineProperty(this, 'ko', { get:() => _ => ko(_), set: ko });
Object.defineProperty(this, 'signal', { get:() => __.a.signal });
Object.defineProperty(this, 'abort', { get:() => __.a.abort() });
Object.defineProperty(this, 'revoke', { get:() => _ => this.revoke = _, set: _ => { __.a.abort(); ko(_) } });
Object.defineProperty(this, 'onabort', { get:() => sig.onabort, set: _ => sig.onabort = _ });
Object.defineProperty(this, 'aborted', { get:() => sig.aborted });
Object.defineProperty(this, 'on', { get:() => (fn,...a) => { const f = _ => fn(...a); sig.addEventListener('abort', f); return () => sig.removeEventListener('abort', f) } });
Object.defineProperty(this, 'settled', { get:() => !__ });
}
};
const R = (...a) => new Revocable(...a);
// Examples:
// for (let i=0; ++i<1000000; ) fetch(`http://example.com/?${i}`); // crashes the Tab
// const fetch50 = Semaphore(50, fetch); // repair
// for (let i=0; ++i<1000000; ) fetch50(`http://example.com/?${i}`); // works
/*
const x = Semaphore(
_ => { console.log('running', _.run); return 10 },
(v,ms) => new Promise(ok => setTimeout(ok, ms, {v, ms})).then(_ => console.log('resolved', _)),
'waiting was'
);
for (let i=100; --i>0; x(Math.random()*10000|0));
*/
/*
const sem = Semaphore(1);
async function task(s)
{
console.log('task', s);
const release = await sem.Acquire(1);
console.log(1, s);
await SleeP(2500);
console.log(2, s);
release();
return s;
}
task('hello').then(console.log);
task('world').then(console.log);
console.log('main');
*/
// Semaphore() returns with following properties:
// .max parameter 1 passed to Semaphore (your chosen max value or function).
// .fn parameter 2 passed to Semaphore (the function protected by Semaphore)
// .args parameter 3+ passed to Semaphore (the first arguments to .fn)
// .count number of currently running calls
// .wait number of currently waiting calls
// .cancel(..) cancels all waiting calls (this rejects their promise!)
// .stop() stops further execution
// .start() starts execution again
//
// .max, .fn and .args can be changed on the fly!
// If .fn is NULL, a call to the return of Semaphore just resolves to the array of given parameters.
//
// .cancel .stop .start are chainable (return the Semaphore)
// .cancel(N,M) cancels waiting tasks with message M. By default this is the array of the second arguments passed to fn.
// .cancel() cancels all
// .cancel(+N) cancels the first N on the waiting list
// .cancel(-N) cancels the last N on the waiting list
// .try() Same as Acquire(), but synchronous. Hence it fails if no Semaphore available (or .max is a function which returns a Promise or throws)
// .acquire() same as .try(). Note that .try() returns void 0 if nothing can be aquired
// .Acquire() acquires 1. returns a Promise which resolves to the "release()" function.
// .Acquire(0) acquires all free (at least 1).
// .Acquire(N,..) and .acquire(N,..) call .max(N,..) if .max is a function
// release() or release(void 0) releases all Acquired, release(1) only releases 1. Throws if "overreleased"
// .Idle() same as .Max()
// .free(N) returns the number of currently free slots (==N if N given). 0 if nothing is free (or N cannot be satisfied). Throws if unsatisfyable
// .Free(N) same as .free() but asynchronous. .Free()/.free() work like .Acquire()/.acquire(), but does not return a release() function
// .WaitN(N) wait for N Releases. .WaitN(0) returns immediately if nothing is running, else waits for 1 Release
// .Max(N) wait until .count is not more than N, .Max() is .Max(0)
// .Min(N) wait until .count is at least N, .Min() is .Min(0)
// Min(0) waits until something happens on the Semaphore
// Min(-1) waits until a Release or Acquire
// Min(-2) waits until an Acquire
// .Waiting(N) wait until .wait <= N
//
// .fifo() switches in FiFo-queuing-strategy (first in, first out), default
// .lifo() switches in LiFo-queuing-strategy (last in, first out)
//
// If .max is a function, it is called with the Semaphore (and optional .Acquire() args) can dynamically return how much work to do in parallel.
// If it returns a Promise, execution halts until the Promise resolves. If it rejects or .max() throws, this is as if it returns 1
// .max() is always called when something happens on the Semaphore (work added, finished, etc.), so it can be used to implement progress monitoring.
//
// JS has no support to abort async functions, hence there is no way to cancel running functions (yet).
// If someone comes up with a good idea on how to cancel async functions, it should be implemented, too.
const Semaphore = (max, fn, ...args) =>
{
const D = DD('Semaphore');
let run = 0;
let maxing; // set while run.max() is awaited
let waiting, cntwait;
const waits = [];
const upd = n =>
{
n = n|0;
ret.count = run += n;
ret.wait = waits.length + (waiting?.count|0);
if (n<0 && waiting)
{
const ok = waiting.ok;
waiting = void 0;
ok(n); // reenable all waiting .Acquires
}
if (cntwait)
{
const ok = cntwait.ok;
cntwait = void 0;
ok(n);
}
}
const check = _ =>
{
D('check', _);
maxing = void 0;
_ = _|0;
if ((_<=0 || run<_) && waits.length)
{
const x = waits.shift();
upd(1);
x[0](x[2]);
}
}
// XXX TODO XXX
// We should call .max() only once (with the same parameters)
// and cache the result until something changes on the Semaphore.
// This also could improve the non-async case in case the async part already has finished.
// (But this perhaps creates some nondeterministic looking behavior on the non-async calls.)
// !! Be prepared that .max() function is only called on changes in future !!
const get = (...a) =>
{
D('get', a);
upd();
try {
return isFunction(ret.max) ? ret.max(ret, ...a) : ret.max;
} catch (e) {
return PE; // This is an internal function, so do not call global error handler in case we are rejected
}
}
const next = _ =>
{
D('next', _);
if (maxing) return upd();
const limit = get();
if (limit?.then)
maxing = Promise.resolve(limit).then(check, _=>check(1)); // in case of max() failing, we just ensure one semaphore runs so this is called again
else
check(limit);
return _;
}
const cancel = (n,msg) =>
{
let _;
if (n === void 0) n = waits.length;
for (n = n|0; n<0 && (_ = waits.pop()) ; n++) _[1](msg || _[2]); // fail promise with msg
for ( ; n>0 && (_ = waits.shift()); n--) _[1](msg || _[2]); // _[1] is ko callback
if (waiting) waiting.ko(msg); // we cannot cancel N here, just all which wait for .Aquire()
return ret;
}
const release_function = n =>
{
// release.left count left
// release.release is the same function such that you can do sem.Acquire(1).then(_ => _.run(fn, ...).release());
// release() releases all
// release(0) does nothing (except updating properties)
function release(k)
{
D('release', k);
if (k===void 0 && !(k=n)) THROW(`Semaphore.release(): already fully released`);
k = k|0;
if (k<0) THROW(`Semaphore.release(${k}): negative`);
if (n<k) THROW(`Semaphore.release(${k}): too high (max ${n})`);
release.left = n -= k;
upd(-k);
return release;
}
upd(n);
release.release = release;
release.run = (fn, ...args) => { CATCH$$(fn, release(0), args); return release(0) }
return release(0);
}
const free = (N,...a) => // .free('1') works. .free('0') throws! This is intended
{
D('try', N,a);
// if (maxing) return; // max is already resolving -> nope, perhaps max() behaves differently here
let n = N === void 0 ? 1 : N|0; // This works for classes with toString() returning nonnull integer
if (!n && N!==0) THROW(`Semaphore: nonnumeric paramter ${N}`);
if (n<0) THROW(`Semaphore: negative parameter ${n}`);
let limit = get(N,...a); // passing N, not n
if (limit?.then) THROW(`Semaphore: cannot use async .max() in non-async call`);
limit = limit|0;
if (!n)
{
if (limit<=0) THROW(`Semaphore: unlimited (.max is ${limit})`);
n = limit-run;
if (n<1) return 0; // Nothing free
}
else if (limit>0)
{
if (n>limit) THROW(`Semaphore: unsatisfyable ${n} (.max is ${limit})`);
if (run+n>limit) return 0; // Not enough free
}
return n;
}
const acquire = (...a) => { const n = free(...a); return n ? release_function(n) : void 0 }
const Waiting = async N =>
{
N = N|0;
while (ret.wait>N)
{
if (!cntwait)
cntwait = PO();
await cntwait.p;
}
return ret;
}
const Max = async N =>
{
N = N|0;
while (ret.count>N)
{
if (!cntwait)
cntwait = PO();
await cntwait.p;
}
return ret;
}
const Min = async N =>
{
N = N|0;
if (N<=0 || ret.count<N)