@fable-org/fable-library-js 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BigInt.js +33 -32
- package/CHANGELOG.md +29 -1
- package/Date.js +1 -1
- package/Double.js +8 -2
- package/Environment.js +3 -0
- package/Event.js +10 -1
- package/File.js +27 -0
- package/List.js +17 -4
- package/MailboxProcessor.js +11 -5
- package/Observable.js +5 -3
- package/Path.js +31 -0
- package/RegExp.js +53 -24
- package/Seq.js +87 -0
- package/Set.js +16 -3
- package/String.js +4 -1
- package/Types.js +12 -12
- package/Util.js +13 -2
- package/package.json +1 -1
package/BigInt.js
CHANGED
|
@@ -99,12 +99,12 @@ export function toIntN_unchecked(bits, x, signed) {
|
|
|
99
99
|
return signed ? BigInt.asIntN(bits, x) : BigInt.asUintN(bits, x);
|
|
100
100
|
}
|
|
101
101
|
export function toIntN(bits, x, signed) {
|
|
102
|
-
|
|
103
|
-
if (
|
|
102
|
+
const truncated = signed ? BigInt.asIntN(bits, x) : BigInt.asUintN(bits, x);
|
|
103
|
+
if (truncated !== x) {
|
|
104
104
|
const s = signed ? "a signed" : "an unsigned";
|
|
105
105
|
throw new Exception(`Value was either too large or too small for ${s} ${bits}-bit integer.`);
|
|
106
106
|
}
|
|
107
|
-
return
|
|
107
|
+
return truncated;
|
|
108
108
|
}
|
|
109
109
|
export function toInt8(x) { return Number(toIntN(8, x, true)); }
|
|
110
110
|
export function toUInt8(x) { return Number(toIntN(8, x, false)); }
|
|
@@ -136,6 +136,9 @@ export function toFloat64(x) { return Number(x); }
|
|
|
136
136
|
export function toDecimal(x) {
|
|
137
137
|
const isNegative = x < zero;
|
|
138
138
|
const bits = abs(x);
|
|
139
|
+
if ((bits >> 96n) !== zero) {
|
|
140
|
+
throw new Exception("Value was either too large or too small for a Decimal.");
|
|
141
|
+
}
|
|
139
142
|
const low = Number(BigInt.asUintN(32, bits));
|
|
140
143
|
const mid = Number(BigInt.asUintN(32, bits >> 32n));
|
|
141
144
|
const high = Number(BigInt.asUintN(32, bits >> 64n));
|
|
@@ -178,6 +181,8 @@ export function divRem(x, y, out) {
|
|
|
178
181
|
}
|
|
179
182
|
}
|
|
180
183
|
export function greatestCommonDivisor(x, y) {
|
|
184
|
+
x = abs(x);
|
|
185
|
+
y = abs(y);
|
|
181
186
|
while (y > zero) {
|
|
182
187
|
const q = x / y;
|
|
183
188
|
const r = x - q * y;
|
|
@@ -186,8 +191,16 @@ export function greatestCommonDivisor(x, y) {
|
|
|
186
191
|
}
|
|
187
192
|
return x;
|
|
188
193
|
}
|
|
194
|
+
// Number of bits in the binary representation of x (x must be positive)
|
|
195
|
+
function bitLength(x) {
|
|
196
|
+
const hex = x.toString(16);
|
|
197
|
+
return (hex.length - 1) * 4 + (32 - Math.clz32(fromHexCode(hex.charCodeAt(0))));
|
|
198
|
+
}
|
|
189
199
|
export function getBitLength(x) {
|
|
190
|
-
|
|
200
|
+
if (x < zero) {
|
|
201
|
+
x = -x - one; // two's complement bit length excluding the sign bit
|
|
202
|
+
}
|
|
203
|
+
return x === zero ? zero : BigInt(bitLength(x));
|
|
191
204
|
}
|
|
192
205
|
export function log2(x) {
|
|
193
206
|
const n = Number(x);
|
|
@@ -219,7 +232,10 @@ export function log(x, base) {
|
|
|
219
232
|
return log2(x) / Math.log2(base);
|
|
220
233
|
}
|
|
221
234
|
export function ilog2(x) {
|
|
222
|
-
|
|
235
|
+
if (x < zero) {
|
|
236
|
+
throw new Exception("Value must be non-negative.");
|
|
237
|
+
}
|
|
238
|
+
return x === zero ? zero : BigInt(bitLength(x) - 1);
|
|
223
239
|
}
|
|
224
240
|
// export function copySign
|
|
225
241
|
// export function createChecked
|
|
@@ -248,35 +264,20 @@ function fromHexCode(code) {
|
|
|
248
264
|
}
|
|
249
265
|
function toSignedBytes(x, isBigEndian) {
|
|
250
266
|
const isNeg = x < 0n;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
x = x
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const first = hex.charCodeAt(0);
|
|
261
|
-
const isLow = 48 <= first && first <= 55; // 0..7
|
|
262
|
-
const start = (isNeg && isLow) || (!isNeg && !isLow) ? 1 : 0;
|
|
263
|
-
const bytes = new Uint8Array(start + (len + odd) / 2);
|
|
264
|
-
const inc = isBigEndian ? 1 : -1;
|
|
265
|
-
let pos = isBigEndian ? 0 : bytes.length - 1;
|
|
266
|
-
if (start > 0) {
|
|
267
|
-
bytes[pos] = isNeg ? 255 : 0;
|
|
268
|
-
pos += inc;
|
|
269
|
-
}
|
|
270
|
-
if (odd > 0) {
|
|
271
|
-
bytes[pos] = fromHexCode(first);
|
|
272
|
-
pos += inc;
|
|
267
|
+
const sentinel = isNeg ? -1n : 0n;
|
|
268
|
+
const bytes = []; // little-endian
|
|
269
|
+
do {
|
|
270
|
+
bytes.push(Number(BigInt.asUintN(8, x)));
|
|
271
|
+
x = x >> 8n;
|
|
272
|
+
} while (x !== sentinel);
|
|
273
|
+
// extra byte if the top byte's sign bit would misrepresent the sign
|
|
274
|
+
if (isNeg !== (bytes[bytes.length - 1] > 127)) {
|
|
275
|
+
bytes.push(isNeg ? 255 : 0);
|
|
273
276
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const b = fromHexCode(hex.charCodeAt(i + 1));
|
|
277
|
-
bytes[pos] = (a << 4) | b;
|
|
277
|
+
if (isBigEndian) {
|
|
278
|
+
bytes.reverse();
|
|
278
279
|
}
|
|
279
|
-
return bytes;
|
|
280
|
+
return new Uint8Array(bytes);
|
|
280
281
|
}
|
|
281
282
|
function fromSignedBytes(bytes, isBigEndian) {
|
|
282
283
|
if (bytes == null) {
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
last_commit_released:
|
|
2
|
+
last_commit_released: d6ae6bd3790b57b31941a118cdffaeb6a59155c3
|
|
3
3
|
updaters:
|
|
4
4
|
- package.json:
|
|
5
5
|
file: package.json
|
|
@@ -15,6 +15,34 @@ All notable changes to this project will be documented in this file.
|
|
|
15
15
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
16
16
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
17
17
|
|
|
18
|
+
## 2.4.1 - 2026-07-09
|
|
19
|
+
|
|
20
|
+
### 🐞 Bug Fixes
|
|
21
|
+
|
|
22
|
+
* MailboxProcessor dropping falsy messages and unit replies ([8299b2e7](https://github.com/fable-compiler/Fable/commit/8299b2e7f690e9136906653155883a08607aa4a4))
|
|
23
|
+
* *(all)* Add `Seq.enumerateTryWith` for try/with in seq, list, array comprehensions (#4750) ([448c90d7](https://github.com/fable-compiler/Fable/commit/448c90d7ccba80ee3d3c38432a2a1d1407f6dc69))
|
|
24
|
+
* *(js/ts)* Match .NET NaN semantics in JS comparison, min and max ([7e5106f9](https://github.com/fable-compiler/Fable/commit/7e5106f9d93cb55ae4aca4656868fa0c53493991))
|
|
25
|
+
* *(js/ts)* BigInteger byte-array corruption, checked conversions, gcd, log2 (#4743) ([f3cb7f2b](https://github.com/fable-compiler/Fable/commit/f3cb7f2bfc9e46e558763867d6c1ba323bcf281c))
|
|
26
|
+
* *(js/ts)* Observable/Event dropping null/undefined/unit values (#4742) ([75bff73b](https://github.com/fable-compiler/Fable/commit/75bff73be3743fe0af37f99c541b10f6978150f3))
|
|
27
|
+
* *(js/ts)* EndsWith regression for empty pattern with non-ordinal comparison ([7793ed7b](https://github.com/fable-compiler/Fable/commit/7793ed7b0fc1f33bf3da6c0ea31607e172726267))
|
|
28
|
+
* *(js/ts/python)* Quote strings with the %A format specifier (#4749) ([75321f3b](https://github.com/fable-compiler/Fable/commit/75321f3b2cb70ea345a0a45d6343cbb6e0de466f))
|
|
29
|
+
* *(ts/js)* `Regex.Replace` count/substitutions and `Regex.Split` semantics (#4741) ([d6ae6bd3](https://github.com/fable-compiler/Fable/commit/d6ae6bd3790b57b31941a118cdffaeb6a59155c3))
|
|
30
|
+
|
|
31
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/7f915f1dd66b9a5fbbd56f858b07d39b98519b65..d6ae6bd3790b57b31941a118cdffaeb6a59155c3)</small></strong>
|
|
32
|
+
|
|
33
|
+
## 2.4.0 - 2026-07-05
|
|
34
|
+
|
|
35
|
+
### 🚀 Features
|
|
36
|
+
|
|
37
|
+
* *(js/ts)* Add `System.IO.Path` and `System.IO.File` API support ([ec99fac5](https://github.com/fable-compiler/Fable/commit/ec99fac5fd7a9428ee54f66f878ef4edad0904cb))
|
|
38
|
+
* *(js/ts/python)* Add Environment and Console.Error support ([6e082f95](https://github.com/fable-compiler/Fable/commit/6e082f955140676da3388a43b55378ea4ad20f1c))
|
|
39
|
+
|
|
40
|
+
### 🐞 Bug Fixes
|
|
41
|
+
|
|
42
|
+
* *(js/ts)* DateTime.ToString() and %A are now consistent across all DateTimeKind values (#4714) ([de98b0e4](https://github.com/fable-compiler/Fable/commit/de98b0e466c8824b0ba8b941d20efae70d232abb))
|
|
43
|
+
|
|
44
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/c977d78b39225a51c7bd051a1fe363ed0ccbe201..7f915f1dd66b9a5fbbd56f858b07d39b98519b65)</small></strong>
|
|
45
|
+
|
|
18
46
|
## 2.3.0 - 2026-06-30
|
|
19
47
|
|
|
20
48
|
### 🚀 Features
|
package/Date.js
CHANGED
|
@@ -478,7 +478,7 @@ function dateToString_Y(date) {
|
|
|
478
478
|
function dateToStringWithKind(date, format) {
|
|
479
479
|
const utc = date.kind === DateTimeKind.Utc;
|
|
480
480
|
if (typeof format !== "string") {
|
|
481
|
-
return
|
|
481
|
+
return dateToString_d(date) + " " + dateToString_T(date);
|
|
482
482
|
}
|
|
483
483
|
else if (format.length === 1) {
|
|
484
484
|
switch (format) {
|
package/Double.js
CHANGED
|
@@ -31,15 +31,21 @@ export function isInfinity(x) {
|
|
|
31
31
|
return x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY;
|
|
32
32
|
}
|
|
33
33
|
export function max(x, y) {
|
|
34
|
-
return x
|
|
34
|
+
return Math.max(x, y);
|
|
35
35
|
}
|
|
36
36
|
export function min(x, y) {
|
|
37
|
-
return x
|
|
37
|
+
return Math.min(x, y);
|
|
38
38
|
}
|
|
39
39
|
export function maxMagnitude(x, y) {
|
|
40
|
+
if (Number.isNaN(x) || Number.isNaN(y)) {
|
|
41
|
+
return NaN;
|
|
42
|
+
}
|
|
40
43
|
return Math.abs(x) > Math.abs(y) ? x : y;
|
|
41
44
|
}
|
|
42
45
|
export function minMagnitude(x, y) {
|
|
46
|
+
if (Number.isNaN(x) || Number.isNaN(y)) {
|
|
47
|
+
return NaN;
|
|
48
|
+
}
|
|
43
49
|
return Math.abs(x) < Math.abs(y) ? x : y;
|
|
44
50
|
}
|
|
45
51
|
export function clamp(x, min, max) {
|
package/Environment.js
ADDED
package/Event.js
CHANGED
|
@@ -18,8 +18,17 @@ export class Event$2 {
|
|
|
18
18
|
return createEvent(h => { this._add(h); }, h => { this._remove(h); });
|
|
19
19
|
}
|
|
20
20
|
Trigger(senderOrValue, valueOrUndefined) {
|
|
21
|
+
// Disambiguate the overloads by arity, not by value: a two-argument call may
|
|
22
|
+
// legitimately pass `undefined` as the event value (e.g. unit events).
|
|
21
23
|
let sender = null;
|
|
22
|
-
|
|
24
|
+
let value;
|
|
25
|
+
if (arguments.length <= 1) {
|
|
26
|
+
value = senderOrValue;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
sender = senderOrValue;
|
|
30
|
+
value = valueOrUndefined;
|
|
31
|
+
}
|
|
23
32
|
this.delegates.forEach(f => { f(sender, value); });
|
|
24
33
|
}
|
|
25
34
|
}
|
package/File.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// This library doesn't depend on @types/node (to stay browser-compatible),
|
|
2
|
+
// so this Node.js built-in import can't be type-checked; the exported
|
|
3
|
+
// functions below still declare their own types for callers to rely on.
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
// byte[] is represented as a plain JS/TS array of numbers, not a typed array
|
|
7
|
+
// (see e.g. BitConverter.ts), so convert the Buffer returned by readFileSync.
|
|
8
|
+
export function readAllBytes(path) {
|
|
9
|
+
return Array.from(readFileSync(path));
|
|
10
|
+
}
|
|
11
|
+
export function readAllLines(path) {
|
|
12
|
+
const lines = readFileSync(path, "utf8").split(/\r\n|\r|\n/);
|
|
13
|
+
if (lines[lines.length - 1] === "") {
|
|
14
|
+
lines.pop();
|
|
15
|
+
}
|
|
16
|
+
return lines;
|
|
17
|
+
}
|
|
18
|
+
export function readAllText(path) {
|
|
19
|
+
return readFileSync(path, "utf8");
|
|
20
|
+
}
|
|
21
|
+
export function writeAllBytes(path, bytes) {
|
|
22
|
+
// Convert back from the plain array representation to a typed array Node's fs accepts.
|
|
23
|
+
writeFileSync(path, Uint8Array.from(bytes));
|
|
24
|
+
}
|
|
25
|
+
export function writeAllLines(path, lines) {
|
|
26
|
+
writeFileSync(path, lines.join("\n"));
|
|
27
|
+
}
|
package/List.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isArrayLike, Exception, defaultOf, toIterator, compare, structuralHash, equals, disposeSafe, getEnumerator } from "./Util.js";
|
|
2
|
+
import { Record, toString } from "./Types.js";
|
|
2
3
|
import { defaultArg, some, value as value_1 } from "./Option.js";
|
|
3
|
-
import { disposeSafe, isArrayLike, Exception, defaultOf, toIterator, getEnumerator, compare, structuralHash, equals } from "./Util.js";
|
|
4
|
-
import { Record } from "./Types.js";
|
|
5
4
|
import { class_type, record_type, option_type } from "./Reflection.js";
|
|
6
5
|
import { SR_inputSequenceTooLong, SR_inputSequenceEmpty, SR_inputMustBeNonNegative, SR_notEnoughElements, SR_differentLengths, SR_keyNotFoundAlt, SR_indexOutOfBounds, SR_inputWasEmpty } from "./Global.js";
|
|
7
6
|
import { KeyNotFoundException_$ctor_Z721C83C5 } from "./System.Collections.Generic.js";
|
|
@@ -15,7 +14,21 @@ export class FSharpList extends Record {
|
|
|
15
14
|
}
|
|
16
15
|
toString() {
|
|
17
16
|
const xs = this;
|
|
18
|
-
|
|
17
|
+
let result = "[";
|
|
18
|
+
let first = true;
|
|
19
|
+
const enumerator = getEnumerator(xs);
|
|
20
|
+
try {
|
|
21
|
+
while (enumerator["System.Collections.IEnumerator.MoveNext"]()) {
|
|
22
|
+
let x = undefined, matchValue = undefined, s = undefined;
|
|
23
|
+
const x_1 = enumerator["System.Collections.Generic.IEnumerator`1.get_Current"]();
|
|
24
|
+
result = ((first ? result : (result + "; ")) + ((x = x_1, (matchValue = x, (typeof matchValue === "string") ? ((s = matchValue, ("\"" + s) + "\"")) : toString(x)))));
|
|
25
|
+
first = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
disposeSafe(enumerator);
|
|
30
|
+
}
|
|
31
|
+
return result + "]";
|
|
19
32
|
}
|
|
20
33
|
Equals(other) {
|
|
21
34
|
const xs = this;
|
package/MailboxProcessor.js
CHANGED
|
@@ -27,7 +27,9 @@ class MailboxQueue {
|
|
|
27
27
|
else {
|
|
28
28
|
delete this.firstAndLast;
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
// Wrap the value so falsy messages (0, false, "", null, undefined/unit)
|
|
31
|
+
// can be distinguished from an empty queue.
|
|
32
|
+
return { value };
|
|
31
33
|
}
|
|
32
34
|
return void 0;
|
|
33
35
|
}
|
|
@@ -41,11 +43,11 @@ export class MailboxProcessor {
|
|
|
41
43
|
}
|
|
42
44
|
function __processEvents($this) {
|
|
43
45
|
if ($this.continuation) {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
+
const dequeued = $this.messages.tryGet();
|
|
47
|
+
if (dequeued !== void 0) {
|
|
46
48
|
const cont = $this.continuation;
|
|
47
49
|
delete $this.continuation;
|
|
48
|
-
cont(value);
|
|
50
|
+
cont(dequeued.value);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
}
|
|
@@ -67,15 +69,19 @@ export function post($this, message) {
|
|
|
67
69
|
}
|
|
68
70
|
export function postAndAsyncReply($this, buildMessage) {
|
|
69
71
|
let result;
|
|
72
|
+
// Use an explicit flag because the reply value itself may be undefined
|
|
73
|
+
// (e.g. AsyncReplyChannel<unit>), which must still complete the async.
|
|
74
|
+
let replied = false;
|
|
70
75
|
let continuation;
|
|
71
76
|
function checkCompletion() {
|
|
72
|
-
if (
|
|
77
|
+
if (replied && continuation !== void 0) {
|
|
73
78
|
continuation(result);
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
const reply = {
|
|
77
82
|
reply: (res) => {
|
|
78
83
|
result = res;
|
|
84
|
+
replied = true;
|
|
79
85
|
checkCompletion();
|
|
80
86
|
},
|
|
81
87
|
};
|
package/Observable.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Choice_tryValueIfChoice1Of2, Choice_tryValueIfChoice2Of2 } from "./Choice.js";
|
|
2
|
-
import { value } from "./Option.js";
|
|
2
|
+
import { some, value } from "./Option.js";
|
|
3
3
|
export class Observer {
|
|
4
4
|
constructor(onNext, onError, onCompleted) {
|
|
5
5
|
this.OnNext = onNext;
|
|
@@ -29,7 +29,7 @@ export function choose(chooser, source) {
|
|
|
29
29
|
} }, observer.OnError), observer.OnError, observer.OnCompleted)));
|
|
30
30
|
}
|
|
31
31
|
export function filter(predicate, source) {
|
|
32
|
-
return choose((x) => predicate(x) ? x :
|
|
32
|
+
return choose((x) => predicate(x) ? some(x) : undefined, source);
|
|
33
33
|
}
|
|
34
34
|
export function map(mapping, source) {
|
|
35
35
|
return new Observable((observer) => source.Subscribe(new Observer((t) => {
|
|
@@ -84,11 +84,13 @@ export function merge(source1, source2) {
|
|
|
84
84
|
export function pairwise(source) {
|
|
85
85
|
return new Observable((observer) => {
|
|
86
86
|
let last;
|
|
87
|
+
let haveLast = false;
|
|
87
88
|
return source.Subscribe(new Observer((next) => {
|
|
88
|
-
if (
|
|
89
|
+
if (haveLast) {
|
|
89
90
|
observer.OnNext([last, next]);
|
|
90
91
|
}
|
|
91
92
|
last = next;
|
|
93
|
+
haveLast = true;
|
|
92
94
|
}, observer.OnError, observer.OnCompleted));
|
|
93
95
|
});
|
|
94
96
|
}
|
package/Path.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// This library doesn't depend on @types/node (to stay browser-compatible),
|
|
2
|
+
// so these Node.js built-in imports can't be type-checked; the exported
|
|
3
|
+
// functions below still declare their own types for callers to rely on.
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { basename, extname, join } from "node:path";
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { writeFileSync } from "node:fs";
|
|
10
|
+
// Mirrors the format of .NET's Path.GetRandomFileName(): 8 random hex chars,
|
|
11
|
+
// a dot, then 3 more random hex chars.
|
|
12
|
+
export function getRandomFileName() {
|
|
13
|
+
const nameBytes = new Uint8Array(4);
|
|
14
|
+
crypto.getRandomValues(nameBytes);
|
|
15
|
+
const name = Array.from(nameBytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
16
|
+
const extBytes = new Uint8Array(2);
|
|
17
|
+
crypto.getRandomValues(extBytes);
|
|
18
|
+
const ext = Array.from(extBytes, (b) => b.toString(16).padStart(2, "0")).join("").slice(0, 3);
|
|
19
|
+
return `${name}.${ext}`;
|
|
20
|
+
}
|
|
21
|
+
export function getFileNameWithoutExtension(path) {
|
|
22
|
+
return basename(path, extname(path));
|
|
23
|
+
}
|
|
24
|
+
export function hasExtension(path) {
|
|
25
|
+
return extname(path) !== "";
|
|
26
|
+
}
|
|
27
|
+
export function getTempFileName() {
|
|
28
|
+
const path = join(tmpdir(), getRandomFileName());
|
|
29
|
+
writeFileSync(path, "");
|
|
30
|
+
return path;
|
|
31
|
+
}
|
package/RegExp.js
CHANGED
|
@@ -105,29 +105,38 @@ export function replace(reg, input, replacement, limit, offset = 0) {
|
|
|
105
105
|
input = tmp;
|
|
106
106
|
limit = undefined;
|
|
107
107
|
}
|
|
108
|
-
if (typeof replacement === "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
108
|
+
if (typeof replacement === "string") {
|
|
109
|
+
const rep = replacement;
|
|
110
|
+
// .NET replacement patterns cannot be rewritten as JS replacement strings unambiguously
|
|
111
|
+
// (e.g. `$$0` must stay literal "$0", `$0` doesn't work with JS regex (see #1155), and
|
|
112
|
+
// `${1}` followed by a digit must not become `$1` + digit), so interpret them with an
|
|
113
|
+
// evaluator instead, honoring the `$$` escape left-to-right
|
|
114
|
+
replacement = (match) => rep.replace(/\$(?:(\$)|(&)|(`)|(')|(\d+)|\{(\d+)\}|\{([^}]+)\})/g, (s, dollar, and, before, after, num, numBraced, name) => {
|
|
115
|
+
if (dollar != null) {
|
|
116
|
+
return "$";
|
|
117
|
+
}
|
|
118
|
+
if (and != null) {
|
|
119
|
+
return match[0];
|
|
120
|
+
}
|
|
121
|
+
if (before != null) {
|
|
122
|
+
return match.input.substring(0, match.index);
|
|
123
|
+
}
|
|
124
|
+
if (after != null) {
|
|
125
|
+
return match.input.substring(match.index + match[0].length);
|
|
126
|
+
}
|
|
127
|
+
const n = num ?? numBraced;
|
|
128
|
+
if (n != null) {
|
|
129
|
+
const i = parseInt(n, 10);
|
|
130
|
+
// Same as .NET: an unmatched group is replaced by an empty string,
|
|
131
|
+
// a nonexistent group number keeps the substitution pattern literally
|
|
132
|
+
return i < match.length ? (match[i] ?? "") : s;
|
|
133
|
+
}
|
|
134
|
+
// Named groups are `${name}` in .NET (in regex: groups are `(?<name>...)` in both)
|
|
135
|
+
return match.groups != null && name in match.groups ? (match.groups[name] ?? "") : s;
|
|
136
|
+
});
|
|
130
137
|
}
|
|
138
|
+
limit = limit == null ? -1 : limit;
|
|
139
|
+
return input.substring(0, offset) + input.substring(offset).replace(reg, replacer);
|
|
131
140
|
}
|
|
132
141
|
export function split(reg, input, limit, offset = 0) {
|
|
133
142
|
if (typeof reg === "string") {
|
|
@@ -136,6 +145,26 @@ export function split(reg, input, limit, offset = 0) {
|
|
|
136
145
|
input = tmp;
|
|
137
146
|
limit = undefined;
|
|
138
147
|
}
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
// JS String.split(regex, limit) truncates the result and discards the remainder,
|
|
149
|
+
// whereas .NET keeps it in the last element (`limit` is the max number of elements),
|
|
150
|
+
// keeps the text before `offset` in the first element and includes the values of
|
|
151
|
+
// matched capture groups, so iterate the matches manually like .NET Regex.Split does
|
|
152
|
+
const result = [];
|
|
153
|
+
let prev = 0;
|
|
154
|
+
let count = limit == null ? -1 : limit - 1;
|
|
155
|
+
for (const m of matches(reg, input, offset)) {
|
|
156
|
+
if (count === 0) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
result.push(input.substring(prev, m.index));
|
|
160
|
+
prev = m.index + m[0].length;
|
|
161
|
+
for (let i = 1; i < m.length; i++) {
|
|
162
|
+
if (m[i] !== undefined) {
|
|
163
|
+
result.push(m[i]);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
count--;
|
|
167
|
+
}
|
|
168
|
+
result.push(input.substring(prev));
|
|
169
|
+
return result;
|
|
141
170
|
}
|
package/Seq.js
CHANGED
|
@@ -457,6 +457,93 @@ export function enumerateUsing(resource, source) {
|
|
|
457
457
|
export function enumerateWhile(guard, xs) {
|
|
458
458
|
return concat(unfold((i) => (guard() ? [xs, i + 1] : undefined), 0));
|
|
459
459
|
}
|
|
460
|
+
export function enumerateTryWith(source, catchFilter, catchHandler) {
|
|
461
|
+
return mkSeq(() => {
|
|
462
|
+
let e = undefined;
|
|
463
|
+
let caught = false;
|
|
464
|
+
let started = false;
|
|
465
|
+
let finished = false;
|
|
466
|
+
let curr = undefined;
|
|
467
|
+
const tryNext = () => {
|
|
468
|
+
try {
|
|
469
|
+
let en_2;
|
|
470
|
+
if (e == null) {
|
|
471
|
+
const en_1 = getEnumerator(source);
|
|
472
|
+
e = en_1;
|
|
473
|
+
en_2 = en_1;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
en_2 = value_1(e);
|
|
477
|
+
}
|
|
478
|
+
if (en_2["System.Collections.IEnumerator.MoveNext"]()) {
|
|
479
|
+
curr = some(en_2["System.Collections.Generic.IEnumerator`1.get_Current"]());
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
finished = true;
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (matchValue) {
|
|
488
|
+
if (!caught) {
|
|
489
|
+
const ex_1 = matchValue;
|
|
490
|
+
if (catchFilter(ex_1) !== 0) {
|
|
491
|
+
caught = true;
|
|
492
|
+
if (e == null) {
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const en_3 = value_1(e);
|
|
496
|
+
try {
|
|
497
|
+
disposeSafe(en_3);
|
|
498
|
+
}
|
|
499
|
+
catch (matchValue_1) {
|
|
500
|
+
}
|
|
501
|
+
e = undefined;
|
|
502
|
+
}
|
|
503
|
+
e = getEnumerator(catchHandler(ex_1));
|
|
504
|
+
return tryNext();
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
throw matchValue;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
throw matchValue;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
return Enumerator_FromFunctions$1_$ctor_58C54629(() => {
|
|
516
|
+
if (!started) {
|
|
517
|
+
Enumerator_notStarted();
|
|
518
|
+
}
|
|
519
|
+
else if (finished) {
|
|
520
|
+
Enumerator_alreadyFinished();
|
|
521
|
+
}
|
|
522
|
+
if (curr != null) {
|
|
523
|
+
return value_1(curr);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
return Enumerator_alreadyFinished();
|
|
527
|
+
}
|
|
528
|
+
}, () => {
|
|
529
|
+
if (!started) {
|
|
530
|
+
started = true;
|
|
531
|
+
}
|
|
532
|
+
if (finished) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
return tryNext();
|
|
537
|
+
}
|
|
538
|
+
}, () => {
|
|
539
|
+
if (e == null) {
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
disposeSafe(value_1(e));
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
}
|
|
460
547
|
export function filter(f, xs) {
|
|
461
548
|
return choose((x) => {
|
|
462
549
|
if (f(x)) {
|
package/Set.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { record_type, bool_type, list_type, option_type, class_type } from "./Reflection.js";
|
|
2
2
|
import { some, value as value_2 } from "./Option.js";
|
|
3
3
|
import { structuralHash, toIterator, disposeSafe, getEnumerator, isArrayLike, Exception } from "./Util.js";
|
|
4
|
-
import { Record } from "./Types.js";
|
|
4
|
+
import { toString, Record } from "./Types.js";
|
|
5
5
|
import { fold as fold_2, cons, singleton as singleton_1, empty as empty_1, ofArrayWithTail, tail, head, isEmpty as isEmpty_1, FSharpList } from "./List.js";
|
|
6
6
|
import { fold as fold_1, fill, setItem } from "./Array.js";
|
|
7
|
-
import { join } from "./String.js";
|
|
8
7
|
import { NotSupportedException_$ctor_Z721C83C5 } from "./System.js";
|
|
9
8
|
import { skip, truncate, iterateIndexed, exists as exists_1, forAll as forAll_1, fold as fold_3, reduce, iterate as iterate_1, map as map_1 } from "./Seq.js";
|
|
10
9
|
import { HashSet__get_Comparer, HashSet_$ctor_Z6150332D, HashSet } from "./MutableSet.js";
|
|
@@ -1402,7 +1401,21 @@ export class FSharpSet {
|
|
|
1402
1401
|
}
|
|
1403
1402
|
toString() {
|
|
1404
1403
|
const this$ = this;
|
|
1405
|
-
|
|
1404
|
+
let result = "set [";
|
|
1405
|
+
let first = true;
|
|
1406
|
+
const enumerator = getEnumerator(this$);
|
|
1407
|
+
try {
|
|
1408
|
+
while (enumerator["System.Collections.IEnumerator.MoveNext"]()) {
|
|
1409
|
+
let x = undefined, matchValue = undefined, s = undefined;
|
|
1410
|
+
const x_1 = enumerator["System.Collections.Generic.IEnumerator`1.get_Current"]();
|
|
1411
|
+
result = ((first ? result : (result + "; ")) + ((x = x_1, (matchValue = x, (typeof matchValue === "string") ? ((s = matchValue, ("\"" + s) + "\"")) : toString(x)))));
|
|
1412
|
+
first = false;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
finally {
|
|
1416
|
+
disposeSafe(enumerator);
|
|
1417
|
+
}
|
|
1418
|
+
return result + "]";
|
|
1406
1419
|
}
|
|
1407
1420
|
get [Symbol.toStringTag]() {
|
|
1408
1421
|
return "FSharpSet";
|
package/String.js
CHANGED
|
@@ -80,7 +80,7 @@ export function endsWith(str, pattern, ic) {
|
|
|
80
80
|
return str.endsWith(pattern);
|
|
81
81
|
}
|
|
82
82
|
if (str.length >= pattern.length) {
|
|
83
|
-
return cmp(str.slice(-pattern.length), pattern, ic) === 0;
|
|
83
|
+
return cmp(str.slice(str.length - pattern.length), pattern, ic) === 0;
|
|
84
84
|
}
|
|
85
85
|
return false;
|
|
86
86
|
}
|
|
@@ -239,6 +239,9 @@ function formatReplacement(rep, flags, padLength, precision, format) {
|
|
|
239
239
|
else if (rep instanceof Date) {
|
|
240
240
|
rep = dateToString(rep);
|
|
241
241
|
}
|
|
242
|
+
else if (format === "A" && typeof rep === "string") {
|
|
243
|
+
rep = "\"" + rep + "\"";
|
|
244
|
+
}
|
|
242
245
|
else {
|
|
243
246
|
rep = toString(rep);
|
|
244
247
|
}
|
package/Types.js
CHANGED
|
@@ -4,19 +4,25 @@ export function seqToString(self) {
|
|
|
4
4
|
let str = "[";
|
|
5
5
|
for (const x of self) {
|
|
6
6
|
if (count === 0) {
|
|
7
|
-
str +=
|
|
7
|
+
str += toStringQuoted(x);
|
|
8
8
|
}
|
|
9
9
|
else if (count === 100) {
|
|
10
10
|
str += "; ...";
|
|
11
11
|
break;
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
|
-
str += "; " +
|
|
14
|
+
str += "; " + toStringQuoted(x);
|
|
15
15
|
}
|
|
16
16
|
count++;
|
|
17
17
|
}
|
|
18
18
|
return str + "]";
|
|
19
19
|
}
|
|
20
|
+
// Structured (%A-style) rendering of a value used as an element/field of a
|
|
21
|
+
// container. F#'s structured formatting (which records, unions, lists, etc.
|
|
22
|
+
// use in their ToString) quotes strings, e.g. `["a"; "b"]` and `{ Name = "John" }`.
|
|
23
|
+
function toStringQuoted(x, callStack = 0) {
|
|
24
|
+
return typeof x === "string" ? "\"" + x + "\"" : toString(x, callStack);
|
|
25
|
+
}
|
|
20
26
|
export function toString(x, callStack = 0) {
|
|
21
27
|
if (x != null && typeof x === "object") {
|
|
22
28
|
if (typeof x.toString === "function" && x.toString !== Object.prototype.toString) {
|
|
@@ -29,19 +35,13 @@ export function toString(x, callStack = 0) {
|
|
|
29
35
|
const cons = Object.getPrototypeOf(x)?.constructor;
|
|
30
36
|
return cons === Object && callStack < 10
|
|
31
37
|
// Same format as recordToString
|
|
32
|
-
? "{ " + Object.entries(x).map(([k, v]) => k + " = " +
|
|
38
|
+
? "{ " + Object.entries(x).map(([k, v]) => k + " = " + toStringQuoted(v, callStack + 1)).join("\n ") + " }"
|
|
33
39
|
: cons?.name ?? "";
|
|
34
40
|
}
|
|
35
41
|
}
|
|
36
42
|
return String(x);
|
|
37
43
|
}
|
|
38
44
|
export function unionToString(name, fields) {
|
|
39
|
-
function unionFieldToString(x) {
|
|
40
|
-
if (typeof x === "string") {
|
|
41
|
-
return '"' + x + '"';
|
|
42
|
-
}
|
|
43
|
-
return toString(x);
|
|
44
|
-
}
|
|
45
45
|
if (fields.length === 0) {
|
|
46
46
|
return name;
|
|
47
47
|
}
|
|
@@ -49,11 +49,11 @@ export function unionToString(name, fields) {
|
|
|
49
49
|
let fieldStr;
|
|
50
50
|
let withParens = true;
|
|
51
51
|
if (fields.length === 1) {
|
|
52
|
-
fieldStr =
|
|
52
|
+
fieldStr = toStringQuoted(fields[0]);
|
|
53
53
|
withParens = fieldStr.indexOf(" ") >= 0;
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
|
-
fieldStr = fields.map((x) =>
|
|
56
|
+
fieldStr = fields.map((x) => toStringQuoted(x)).join(", ");
|
|
57
57
|
}
|
|
58
58
|
return name + (withParens ? " (" : " ") + fieldStr + (withParens ? ")" : "");
|
|
59
59
|
}
|
|
@@ -111,7 +111,7 @@ function recordToJSON(self) {
|
|
|
111
111
|
return o;
|
|
112
112
|
}
|
|
113
113
|
function recordToString(self) {
|
|
114
|
-
return "{ " + Object.entries(self).map(([k, v]) => k + " = " +
|
|
114
|
+
return "{ " + Object.entries(self).map(([k, v]) => k + " = " + toStringQuoted(v)).join("\n ") + " }";
|
|
115
115
|
}
|
|
116
116
|
function recordGetHashCode(self) {
|
|
117
117
|
const hashes = Object.values(self).map((v) => structuralHash(v));
|
package/Util.js
CHANGED
|
@@ -404,7 +404,18 @@ export function compareDates(x, y) {
|
|
|
404
404
|
return xtime === ytime ? 0 : (xtime < ytime ? -1 : 1);
|
|
405
405
|
}
|
|
406
406
|
export function comparePrimitives(x, y) {
|
|
407
|
-
|
|
407
|
+
if (x === y) {
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
if (x < y) {
|
|
411
|
+
return -1;
|
|
412
|
+
}
|
|
413
|
+
if (x > y) {
|
|
414
|
+
return 1;
|
|
415
|
+
}
|
|
416
|
+
// Neither equal nor ordered: at least one operand is NaN.
|
|
417
|
+
// Match .NET Double.CompareTo: NaN equals NaN and is less than any other value.
|
|
418
|
+
return Number.isNaN(x) ? (Number.isNaN(y) ? 0 : -1) : 1;
|
|
408
419
|
}
|
|
409
420
|
export function compareArraysWith(x, y, comp) {
|
|
410
421
|
if (x == null) {
|
|
@@ -466,7 +477,7 @@ export function compare(x, y) {
|
|
|
466
477
|
return isArrayLike(y) ? compareArrays(x, y) : -1;
|
|
467
478
|
}
|
|
468
479
|
else if (typeof x !== "object") {
|
|
469
|
-
return x
|
|
480
|
+
return comparePrimitives(x, y);
|
|
470
481
|
}
|
|
471
482
|
else if (x instanceof Date) {
|
|
472
483
|
return y instanceof Date ? compareDates(x, y) : -1;
|
package/package.json
CHANGED