@fable-org/fable-library-ts 2.0.0-rc.4 → 2.0.0-rc.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/Array.ts CHANGED
@@ -1,12 +1,13 @@
1
1
 
2
2
  import { IComparer, IDisposable, disposeSafe, IEnumerator, getEnumerator, copyToArray, IEqualityComparer, defaultOf, MutableArray, Exception } from "./Util.ts";
3
- import { int32 } from "./Int32.ts";
3
+ import { float64, int32 } from "./Int32.ts";
4
4
  import { Helpers_allocateArrayFromCons } from "./Native.ts";
5
5
  import { setItem as setItem_1, item as item_2 } from "./Array.ts";
6
6
  import { value as value_2, map as map_1, defaultArg, Option, some } from "./Option.ts";
7
7
  import { min as min_1, max as max_1 } from "./Double.ts";
8
- import { SR_indexOutOfBounds } from "./Global.ts";
8
+ import { SR_notEnoughElements, SR_inputMustBeNonNegative, SR_inputSequenceEmpty, SR_Arg_ArgumentOutOfRangeException, SR_indexOutOfBounds } from "./Global.ts";
9
9
  import { Operators_IsNull } from "./FSharp.Core.ts";
10
+ import { nonSeeded } from "./Random.ts";
10
11
  import { FSharpRef } from "./Types.ts";
11
12
 
12
13
  function indexNotFound<$a>(): $a {
@@ -1307,6 +1308,120 @@ export function insertManyAt<T>(index: int32, ys: Iterable<T>, xs: MutableArray<
1307
1308
  return target;
1308
1309
  }
1309
1310
 
1311
+ export function randomShuffleInPlaceBy<T>(randomizer: (() => float64), xs: MutableArray<T>): void {
1312
+ const len: int32 = xs.length | 0;
1313
+ for (let i: int32 = len - 1; i >= 1; i--) {
1314
+ const r: float64 = randomizer();
1315
+ if ((r < 0) ? true : (r >= 1)) {
1316
+ throw new Exception((SR_Arg_ArgumentOutOfRangeException + "\\nParameter name: ") + "randomizer");
1317
+ }
1318
+ const j: int32 = ~~(r * (i + 1)) | 0;
1319
+ const tmp: T = item_2(i, xs);
1320
+ setItem_1(xs, i, item_2(j, xs));
1321
+ setItem_1(xs, j, tmp);
1322
+ }
1323
+ }
1324
+
1325
+ export function randomShuffleInPlaceWith<T>(random: any, xs: MutableArray<T>): void {
1326
+ randomShuffleInPlaceBy<T>((): float64 => random.NextDouble(), xs);
1327
+ }
1328
+
1329
+ export function randomShuffleInPlace<T>(xs: MutableArray<T>): void {
1330
+ randomShuffleInPlaceWith<T>(nonSeeded(), xs);
1331
+ }
1332
+
1333
+ export function randomShuffleBy<T>(randomizer: (() => float64), xs: MutableArray<T>): MutableArray<T> {
1334
+ const arr: MutableArray<T> = copy<T>(xs);
1335
+ randomShuffleInPlaceBy<T>(randomizer, arr);
1336
+ return arr;
1337
+ }
1338
+
1339
+ export function randomShuffleWith<T>(random: any, xs: MutableArray<T>): MutableArray<T> {
1340
+ return randomShuffleBy<T>((): float64 => random.NextDouble(), xs);
1341
+ }
1342
+
1343
+ export function randomShuffle<T>(xs: MutableArray<T>): MutableArray<T> {
1344
+ return randomShuffleWith<T>(nonSeeded(), xs);
1345
+ }
1346
+
1347
+ export function randomChoiceBy<T>(randomizer: (() => float64), xs: MutableArray<T>): T {
1348
+ if (isEmpty<T>(xs)) {
1349
+ throw new Exception((SR_inputSequenceEmpty + "\\nParameter name: ") + "source");
1350
+ }
1351
+ const len: int32 = xs.length | 0;
1352
+ const r: float64 = randomizer();
1353
+ if ((r < 0) ? true : (r >= 1)) {
1354
+ throw new Exception((SR_Arg_ArgumentOutOfRangeException + "\\nParameter name: ") + "randomizer");
1355
+ }
1356
+ return item_2(~~(r * len), xs);
1357
+ }
1358
+
1359
+ export function randomChoiceWith<T>(random: any, xs: MutableArray<T>): T {
1360
+ return randomChoiceBy<T>((): float64 => random.NextDouble(), xs);
1361
+ }
1362
+
1363
+ export function randomChoice<T>(xs: MutableArray<T>): T {
1364
+ return randomChoiceWith<T>(nonSeeded(), xs);
1365
+ }
1366
+
1367
+ export function randomChoicesBy<T>(randomizer: (() => float64), count: int32, xs: MutableArray<T>, cons?: any): MutableArray<T> {
1368
+ if (count < 0) {
1369
+ throw new Exception((SR_inputMustBeNonNegative + "\\nParameter name: ") + "count");
1370
+ }
1371
+ if ((count > 0) && isEmpty<T>(xs)) {
1372
+ throw new Exception((SR_inputSequenceEmpty + "\\nParameter name: ") + "source");
1373
+ }
1374
+ const len: int32 = xs.length | 0;
1375
+ return initialize<T>(count, (_arg: int32): T => {
1376
+ const r: float64 = randomizer();
1377
+ if ((r < 0) ? true : (r >= 1)) {
1378
+ throw new Exception((SR_Arg_ArgumentOutOfRangeException + "\\nParameter name: ") + "randomizer");
1379
+ }
1380
+ return item_2(~~(r * len), xs);
1381
+ }, cons);
1382
+ }
1383
+
1384
+ export function randomChoicesWith<T>(random: any, count: int32, xs: MutableArray<T>, cons?: any): MutableArray<T> {
1385
+ return randomChoicesBy<T>((): float64 => random.NextDouble(), count, xs, cons);
1386
+ }
1387
+
1388
+ export function randomChoices<T>(count: int32, xs: MutableArray<T>, cons?: any): MutableArray<T> {
1389
+ return randomChoicesWith<T>(nonSeeded(), count, xs, cons);
1390
+ }
1391
+
1392
+ export function randomSampleBy<T>(randomizer: (() => float64), count: int32, xs: MutableArray<T>): MutableArray<T> {
1393
+ if (count < 0) {
1394
+ throw new Exception((SR_inputMustBeNonNegative + "\\nParameter name: ") + "count");
1395
+ }
1396
+ const arr: MutableArray<T> = copy<T>(xs);
1397
+ const len: int32 = arr.length | 0;
1398
+ if ((len === 0) && (count > 0)) {
1399
+ throw new Exception((SR_inputSequenceEmpty + "\\nParameter name: ") + "source");
1400
+ }
1401
+ if (count > len) {
1402
+ throw new Exception((SR_notEnoughElements + "\\nParameter name: ") + "count");
1403
+ }
1404
+ for (let i = 0; i <= (count - 1); i++) {
1405
+ const r: float64 = randomizer();
1406
+ if ((r < 0) ? true : (r >= 1)) {
1407
+ throw new Exception((SR_Arg_ArgumentOutOfRangeException + "\\nParameter name: ") + "randomizer");
1408
+ }
1409
+ const j: int32 = (i + ~~(r * (len - i))) | 0;
1410
+ const tmp: T = item_2(i, arr);
1411
+ setItem_1(arr, i, item_2(j, arr));
1412
+ setItem_1(arr, j, tmp);
1413
+ }
1414
+ return getSubArray<T>(arr, 0, count);
1415
+ }
1416
+
1417
+ export function randomSampleWith<T>(random: any, count: int32, xs: MutableArray<T>): MutableArray<T> {
1418
+ return randomSampleBy<T>((): float64 => random.NextDouble(), count, xs);
1419
+ }
1420
+
1421
+ export function randomSample<T>(count: int32, xs: MutableArray<T>): MutableArray<T> {
1422
+ return randomSampleWith<T>(nonSeeded(), count, xs);
1423
+ }
1424
+
1310
1425
  export function removeAt<T>(index: int32, xs: MutableArray<T>): MutableArray<T> {
1311
1426
  if ((index < 0) ? true : (index >= xs.length)) {
1312
1427
  throw new Exception((SR_indexOutOfBounds + "\\nParameter name: ") + "index");
package/Async.ts CHANGED
@@ -58,37 +58,25 @@ export function throwIfCancellationRequested(token: CancellationToken) {
58
58
  }
59
59
  }
60
60
 
61
- function throwAfter(millisecondsDueTime: number): Async<void> {
62
- return protectedCont((ctx: IAsyncContext<void>) => {
63
- let tokenId: number;
64
- const timeoutId = setTimeout(() => {
65
- ctx.cancelToken.removeListener(tokenId);
66
- ctx.onError(TimeoutException_$ctor());
67
- }, millisecondsDueTime);
68
- tokenId = ctx.cancelToken.addListener(() => {
69
- clearTimeout(timeoutId);
70
- ctx.onCancel(new OperationCanceledException());
71
- });
72
- });
73
- }
74
-
75
61
  export function startChild<T>(computation: Async<T>, ms?: number): Async<Async<T>> {
76
- if (ms) {
77
- const computationWithTimeout = protectedBind(
78
- parallel2(
79
- computation,
80
- throwAfter(ms)),
81
- xs => protectedReturn(xs[0]));
62
+ const promise = startAsPromise(computation);
63
+ let promiseToRun = promise;
82
64
 
83
- return startChild(computationWithTimeout);
65
+ if (ms) {
66
+ // Race the computation against a timeout: whichever settles first wins.
67
+ promiseToRun = new Promise<T>((resolve, reject) => {
68
+ const timeoutId = setTimeout(() => reject(TimeoutException_$ctor()), ms);
69
+ promise.then(
70
+ value => { clearTimeout(timeoutId); resolve(value); },
71
+ error => { clearTimeout(timeoutId); reject(error); }
72
+ );
73
+ });
84
74
  }
85
75
 
86
- const promise = startAsPromise(computation);
87
-
88
76
  // JS Promises are hot, computation has already started
89
77
  // but we delay returning the result
90
78
  return protectedCont((ctx) =>
91
- protectedReturn(awaitPromise(promise))(ctx));
79
+ protectedReturn(awaitPromise(promiseToRun))(ctx));
92
80
  }
93
81
 
94
82
  export function awaitPromise<T>(p: Promise<T>) {
@@ -129,10 +117,6 @@ export function parallel<T>(computations: Iterable<Async<T>>) {
129
117
  return delay(() => awaitPromise(Promise.all(Array.from(computations, (w) => startAsPromise(w)))));
130
118
  }
131
119
 
132
- function parallel2<T, U>(a: Async<T>, b: Async<U>): Async<[T, U]> {
133
- return delay(() => awaitPromise(Promise.all([startAsPromise(a), startAsPromise(b)])));
134
- }
135
-
136
120
  export function sequential<T>(computations: Iterable<Async<T>>) {
137
121
 
138
122
  function _sequential<T>(computations: Iterable<Async<T>>): Promise<T[]> {
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 2.0.0-rc.6 - 2026-04-07
11
+
12
+ ### Fixed
13
+
14
+ * [JS/TS] Fix `Async.StartChild` with timeout always timing out even when the computation finishes before the deadline (fixes #4481) (by @MangelMaxime)
15
+
16
+ ## 2.0.0-rc.5 - 2026-03-31
17
+
18
+ ### Fixed
19
+
20
+ * [JS/TS] Improve `Regex.Escape` and `Regex.Unescape` handling (by @MangelMaxime)
21
+ * [JS/TS] Fixed quotation for union string cases (by @MangelMaxime)
22
+
10
23
  ## 2.0.0-rc.4 - 2026-03-19
11
24
 
12
25
  ### Fixed
package/Guid.ts CHANGED
@@ -63,15 +63,38 @@ export function tryParse(str: string, defValue: FSharpRef<string>): boolean {
63
63
  }
64
64
  }
65
65
 
66
- // From https://gist.github.com/LeverOne/1308368
67
66
  export function newGuid() {
68
- let b = "";
69
- for (let a = 0; a++ < 36;) {
70
- b += a * 51 & 52
71
- ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16)
72
- : "-";
73
- }
74
- return b;
67
+ const bytes = new Uint8Array(16);
68
+ crypto.getRandomValues(bytes);
69
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
70
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1
71
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
72
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
73
+ }
74
+
75
+ // RFC 9562 UUID v7
76
+ export function createVersion7(timestamp?: Date): string {
77
+ const ms = timestamp != null ? timestamp.getTime() : Date.now();
78
+
79
+ // 48-bit timestamp as hex
80
+ const msHex = Math.floor(ms).toString(16).padStart(12, "0");
81
+
82
+ // random bits
83
+ const bytes = new Uint8Array(10);
84
+ crypto.getRandomValues(bytes);
85
+ const view = new DataView(bytes.buffer);
86
+ const randA = view.getUint16(0) & 0x0fff; // 12 bits
87
+ const randB1 = view.getUint16(2) & 0x3fff; // 14 bits
88
+ const randB2 = view.getUint32(4); // 32 bits
89
+ const randB3 = view.getUint16(8); // 16 bits
90
+
91
+ const timeLow = msHex.slice(0, 8);
92
+ const timeMid = msHex.slice(8, 12);
93
+ const ver = (0x7000 | randA).toString(16).padStart(4, "0");
94
+ const variantAndRandB = (0x8000 | randB1).toString(16).padStart(4, "0");
95
+ const node = randB2.toString(16).padStart(8, "0") + randB3.toString(16).padStart(4, "0");
96
+
97
+ return `${timeLow}-${timeMid}-${ver}-${variantAndRandB}-${node}`;
75
98
  }
76
99
 
77
100
  // Maps for number <-> hex string conversion
package/List.ts CHANGED
@@ -2,12 +2,13 @@
2
2
  import { join } from "./String.ts";
3
3
  import { defaultArg, some, value as value_1, Option } from "./Option.ts";
4
4
  import { IComparer, IEqualityComparer, disposeSafe, isArrayLike, MutableArray, Exception, IDisposable, defaultOf, toIterator, getEnumerator, IEnumerator, compare, structuralHash, equals } from "./Util.ts";
5
- import { int32 } from "./Int32.ts";
5
+ import { float64, int32 } from "./Int32.ts";
6
6
  import { Record } from "./Types.ts";
7
7
  import { class_type, record_type, option_type, TypeInfo } from "./Reflection.ts";
8
8
  import { SR_inputSequenceTooLong, SR_inputSequenceEmpty, SR_inputMustBeNonNegative, SR_notEnoughElements, SR_differentLengths, SR_keyNotFoundAlt, SR_indexOutOfBounds, SR_inputWasEmpty } from "./Global.ts";
9
9
  import { KeyNotFoundException_$ctor_Z721C83C5 } from "./System.Collections.Generic.ts";
10
- import { transpose as transpose_1, splitInto as splitInto_1, windowed as windowed_1, pairwise as pairwise_1, chunkBySize as chunkBySize_1, map as map_1, permute as permute_1, tryFindIndexBack as tryFindIndexBack_1, tryFindBack as tryFindBack_1, scanBack as scanBack_1, item as item_1, foldBack2 as foldBack2_1, foldBack as foldBack_1, setItem, fill } from "./Array.ts";
10
+ import { randomSampleBy as randomSampleBy_1, randomChoicesBy as randomChoicesBy_1, randomChoiceBy as randomChoiceBy_1, randomShuffleInPlaceBy, transpose as transpose_1, splitInto as splitInto_1, windowed as windowed_1, pairwise as pairwise_1, chunkBySize as chunkBySize_1, map as map_1, permute as permute_1, tryFindIndexBack as tryFindIndexBack_1, tryFindBack as tryFindBack_1, scanBack as scanBack_1, item as item_1, foldBack2 as foldBack2_1, foldBack as foldBack_1, setItem, fill } from "./Array.ts";
11
+ import { nonSeeded } from "./Random.ts";
11
12
 
12
13
  export class FSharpList<T> extends Record implements Iterable<T> {
13
14
  readonly head: T;
@@ -1427,3 +1428,53 @@ export function updateAt<T>(index: int32, y: T, xs: FSharpList<T>): FSharpList<T
1427
1428
  return ys;
1428
1429
  }
1429
1430
 
1431
+ export function randomShuffleBy<T>(randomizer: (() => float64), xs: FSharpList<T>): FSharpList<T> {
1432
+ const arr: MutableArray<T> = toArray<T>(xs);
1433
+ randomShuffleInPlaceBy<T>(randomizer, arr);
1434
+ return ofArray<T>(arr);
1435
+ }
1436
+
1437
+ export function randomShuffleWith<T>(random: any, xs: FSharpList<T>): FSharpList<T> {
1438
+ return randomShuffleBy<T>((): float64 => random.NextDouble(), xs);
1439
+ }
1440
+
1441
+ export function randomShuffle<T>(xs: FSharpList<T>): FSharpList<T> {
1442
+ return randomShuffleWith<T>(nonSeeded(), xs);
1443
+ }
1444
+
1445
+ export function randomChoiceBy<T>(randomizer: (() => float64), xs: FSharpList<T>): T {
1446
+ return randomChoiceBy_1<T>(randomizer, toArray<T>(xs));
1447
+ }
1448
+
1449
+ export function randomChoiceWith<T>(random: any, xs: FSharpList<T>): T {
1450
+ return randomChoiceBy<T>((): float64 => random.NextDouble(), xs);
1451
+ }
1452
+
1453
+ export function randomChoice<T>(xs: FSharpList<T>): T {
1454
+ return randomChoiceWith<T>(nonSeeded(), xs);
1455
+ }
1456
+
1457
+ export function randomChoicesBy<T>(randomizer: (() => float64), count: int32, xs: FSharpList<T>): FSharpList<T> {
1458
+ return ofArray<T>(randomChoicesBy_1<T>(randomizer, count, toArray<T>(xs)));
1459
+ }
1460
+
1461
+ export function randomChoicesWith<T>(random: any, count: int32, xs: FSharpList<T>): FSharpList<T> {
1462
+ return randomChoicesBy<T>((): float64 => random.NextDouble(), count, xs);
1463
+ }
1464
+
1465
+ export function randomChoices<T>(count: int32, xs: FSharpList<T>): FSharpList<T> {
1466
+ return randomChoicesWith<T>(nonSeeded(), count, xs);
1467
+ }
1468
+
1469
+ export function randomSampleBy<T>(randomizer: (() => float64), count: int32, xs: FSharpList<T>): FSharpList<T> {
1470
+ return ofArray<T>(randomSampleBy_1<T>(randomizer, count, toArray<T>(xs)));
1471
+ }
1472
+
1473
+ export function randomSampleWith<T>(random: any, count: int32, xs: FSharpList<T>): FSharpList<T> {
1474
+ return randomSampleBy<T>((): float64 => random.NextDouble(), count, xs);
1475
+ }
1476
+
1477
+ export function randomSample<T>(count: int32, xs: FSharpList<T>): FSharpList<T> {
1478
+ return randomSampleWith<T>(nonSeeded(), count, xs);
1479
+ }
1480
+
package/RegExp.ts CHANGED
@@ -22,11 +22,20 @@ export function create(pattern: string, options = 0) {
22
22
  // From http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
23
23
 
24
24
  export function escape(str: string) {
25
- return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
25
+ // Matches the characters escaped by .NET's Regex.Escape.
26
+ // Note:
27
+ //
28
+ // .NET also escapes space and # (relevant for IgnorePatternWhitespace mode),
29
+ // but JS unicode-mode regex rejects \ and \# as invalid escapes, and we don't
30
+ // support IgnorePatternWhitespace, so we omit them.
31
+ //
32
+ // .NET does not escape ] and } but JS unicode-mode regex rejects bare ] and }
33
+ // as invalid, so we escape them too for compatibility.
34
+ return str.replace(/[$()*+.?[\\\^{|}\]]/g, "\\$&");
26
35
  }
27
36
 
28
37
  export function unescape(str: string) {
29
- return str.replace(/\\([\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|])/g, "$1");
38
+ return str.replace(/\\([$()*+.?[\\\^{|}\]])/g, "$1");
30
39
  }
31
40
 
32
41
  export function isMatch(reg: RegExp, input: string, startAt = 0) {
package/Seq.ts CHANGED
@@ -6,10 +6,11 @@ import { class_type, TypeInfo } from "./Reflection.ts";
6
6
  import { some, value as value_1, Option } from "./Option.ts";
7
7
  import { KeyNotFoundException_$ctor_Z721C83C5 } from "./System.Collections.Generic.ts";
8
8
  import { Operators_Lock, Operators_NullArgCheck } from "./FSharp.Core.ts";
9
- import { chunkBySize as chunkBySize_1, permute as permute_1, transpose as transpose_1, map as map_1, windowed as windowed_1, splitInto as splitInto_1, pairwise as pairwise_1, scanBack as scanBack_1, reverse as reverse_1, mapFoldBack as mapFoldBack_1, mapFold as mapFold_1, item as item_1, tryItem as tryItem_1, tryHead as tryHead_1, foldBack2 as foldBack2_1, foldBack as foldBack_1, tryFindIndexBack as tryFindIndexBack_1, tryFindBack as tryFindBack_1, singleton as singleton_1 } from "./Array.ts";
9
+ import { randomSampleBy as randomSampleBy_1, randomChoicesBy as randomChoicesBy_1, randomChoice as randomChoice_1, randomChoiceWith as randomChoiceWith_1, randomChoiceBy as randomChoiceBy_1, randomShuffleInPlaceBy, chunkBySize as chunkBySize_1, permute as permute_1, transpose as transpose_1, map as map_1, windowed as windowed_1, splitInto as splitInto_1, pairwise as pairwise_1, scanBack as scanBack_1, reverse as reverse_1, mapFoldBack as mapFoldBack_1, mapFold as mapFold_1, item as item_1, tryItem as tryItem_1, tryHead as tryHead_1, foldBack2 as foldBack2_1, foldBack as foldBack_1, tryFindIndexBack as tryFindIndexBack_1, tryFindBack as tryFindBack_1, singleton as singleton_1 } from "./Array.ts";
10
10
  import { length as length_1, tryItem as tryItem_2, isEmpty as isEmpty_1, tryHead as tryHead_2, ofSeq as ofSeq_1, ofArray as ofArray_1, toArray as toArray_1, FSharpList } from "./List.ts";
11
- import { int32 } from "./Int32.ts";
11
+ import { float64, int32 } from "./Int32.ts";
12
12
  import { SR_indexOutOfBounds } from "./Global.ts";
13
+ import { nonSeeded } from "./Random.ts";
13
14
 
14
15
  export const SR_enumerationAlreadyFinished = "Enumeration already finished.";
15
16
 
@@ -1515,3 +1516,53 @@ export function updateAt<T>(index: int32, y: T, xs: Iterable<T>): Iterable<T> {
1515
1516
  });
1516
1517
  }
1517
1518
 
1519
+ export function randomShuffleBy<T>(randomizer: (() => float64), xs: Iterable<T>): Iterable<T> {
1520
+ const arr: MutableArray<T> = toArray<T>(xs);
1521
+ randomShuffleInPlaceBy<T>(randomizer, arr);
1522
+ return ofArray<T>(arr);
1523
+ }
1524
+
1525
+ export function randomShuffleWith<T>(random: any, xs: Iterable<T>): Iterable<T> {
1526
+ return randomShuffleBy<T>((): float64 => random.NextDouble(), xs);
1527
+ }
1528
+
1529
+ export function randomShuffle<T>(xs: Iterable<T>): Iterable<T> {
1530
+ return randomShuffleWith<T>(nonSeeded(), xs);
1531
+ }
1532
+
1533
+ export function randomChoiceBy<T>(randomizer: (() => float64), xs: Iterable<T>): T {
1534
+ return randomChoiceBy_1<T>(randomizer, toArray<T>(xs));
1535
+ }
1536
+
1537
+ export function randomChoiceWith<T>(random: any, xs: Iterable<T>): T {
1538
+ return randomChoiceWith_1<T>(random, toArray<T>(xs));
1539
+ }
1540
+
1541
+ export function randomChoice<T>(xs: Iterable<T>): T {
1542
+ return randomChoice_1<T>(toArray<T>(xs));
1543
+ }
1544
+
1545
+ export function randomChoicesBy<T>(randomizer: (() => float64), count: int32, xs: Iterable<T>): Iterable<T> {
1546
+ return ofArray<T>(randomChoicesBy_1<T>(randomizer, count, toArray<T>(xs)));
1547
+ }
1548
+
1549
+ export function randomChoicesWith<T>(random: any, count: int32, xs: Iterable<T>): Iterable<T> {
1550
+ return randomChoicesBy<T>((): float64 => random.NextDouble(), count, xs);
1551
+ }
1552
+
1553
+ export function randomChoices<T>(count: int32, xs: Iterable<T>): Iterable<T> {
1554
+ return randomChoicesWith<T>(nonSeeded(), count, xs);
1555
+ }
1556
+
1557
+ export function randomSampleBy<T>(randomizer: (() => float64), count: int32, xs: Iterable<T>): Iterable<T> {
1558
+ return ofArray<T>(randomSampleBy_1<T>(randomizer, count, toArray<T>(xs)));
1559
+ }
1560
+
1561
+ export function randomSampleWith<T>(random: any, count: int32, xs: Iterable<T>): Iterable<T> {
1562
+ return randomSampleBy<T>((): float64 => random.NextDouble(), count, xs);
1563
+ }
1564
+
1565
+ export function randomSample<T>(count: int32, xs: Iterable<T>): Iterable<T> {
1566
+ return randomSampleWith<T>(nonSeeded(), count, xs);
1567
+ }
1568
+
package/String.ts CHANGED
@@ -344,7 +344,11 @@ export function format(str: string | object, ...args: any[]) {
344
344
  precision = precision == null ? 2 : precision;
345
345
  rep = toFixed(rep, precision);
346
346
  parts = splitIntAndDecimalPart(rep);
347
- rep = "¤" + thousandSeparate(parts.integral) + "." + padRight(parts.decimal, precision, "0");
347
+ if (precision > 0) {
348
+ rep = "¤" + thousandSeparate(parts.integral) + "." + padRight(parts.decimal, precision, "0");
349
+ } else {
350
+ rep = "¤" + thousandSeparate(parts.integral);
351
+ }
348
352
  if (isNegative) {
349
353
  rep = "(" + rep + ")";
350
354
  }
@@ -382,13 +386,21 @@ export function format(str: string | object, ...args: any[]) {
382
386
  precision = precision != null ? precision : 2;
383
387
  rep = toFixed(rep, precision);
384
388
  parts = splitIntAndDecimalPart(rep);
385
- rep = thousandSeparate(parts.integral) + "." + padRight(parts.decimal, precision, "0");
389
+ if (precision > 0) {
390
+ rep = thousandSeparate(parts.integral) + "." + padRight(parts.decimal, precision, "0");
391
+ } else {
392
+ rep = thousandSeparate(parts.integral);
393
+ }
386
394
  break;
387
395
  case "p": case "P":
388
396
  precision = precision != null ? precision : 2;
389
397
  rep = toFixed(multiply(rep, 100), precision)
390
398
  parts = splitIntAndDecimalPart(rep);
391
- rep = thousandSeparate(parts.integral) + "." + padRight(parts.decimal, precision, "0") + " %";
399
+ if (precision > 0) {
400
+ rep = thousandSeparate(parts.integral) + "." + padRight(parts.decimal, precision, "0") + " %";
401
+ } else {
402
+ rep = thousandSeparate(parts.integral) + " %";
403
+ }
392
404
  break;
393
405
  case "r": case "R":
394
406
  throw new Exception("The round-trip format is not supported by Fable");
package/Types.ts CHANGED
@@ -39,16 +39,23 @@ export function toString(x: any, callStack = 0): string {
39
39
  }
40
40
 
41
41
  export function unionToString(name: string, fields: any[]) {
42
+ function unionFieldToString(x: any): string {
43
+ if (typeof x === "string") {
44
+ return '"' + x + '"';
45
+ }
46
+ return toString(x);
47
+ }
48
+
42
49
  if (fields.length === 0) {
43
50
  return name;
44
51
  } else {
45
52
  let fieldStr;
46
53
  let withParens = true;
47
54
  if (fields.length === 1) {
48
- fieldStr = toString(fields[0]);
55
+ fieldStr = unionFieldToString(fields[0]);
49
56
  withParens = fieldStr.indexOf(" ") >= 0;
50
57
  } else {
51
- fieldStr = fields.map((x: any) => toString(x)).join(", ");
58
+ fieldStr = fields.map((x: any) => unionFieldToString(x)).join(", ");
52
59
  }
53
60
  return name + (withParens ? " (" : " ") + fieldStr + (withParens ? ")" : "");
54
61
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "name": "@fable-org/fable-library-ts",
6
- "version": "2.0.0-rc.4",
6
+ "version": "2.0.0-rc.6",
7
7
  "description": "Core library used by F# projects compiled with fable.io",
8
8
  "author": "Fable Contributors",
9
9
  "license": "MIT",
package/quotation.ts ADDED
@@ -0,0 +1,644 @@
1
+ /**
2
+ * F# Quotation runtime support for Fable JS/TS target.
3
+ *
4
+ * Provides class-based representations of F# quotation AST nodes
5
+ * and pattern matching helpers compatible with Fable's option convention
6
+ * (undefined = no match, value/tuple = match).
7
+ */
8
+
9
+ // ===================================================================
10
+ // Var: represents an F# quotation variable
11
+ // ===================================================================
12
+
13
+ export class Var {
14
+ readonly Name: string;
15
+ readonly Type: string;
16
+ readonly IsMutable: boolean;
17
+
18
+ constructor(name: string, type_: string, isMutable: boolean) {
19
+ this.Name = name;
20
+ this.Type = type_;
21
+ this.IsMutable = isMutable;
22
+ }
23
+ }
24
+
25
+ export function mkQuotVar(name: string, type_: string, isMutable: boolean = false): Var {
26
+ return new Var(name, type_, isMutable);
27
+ }
28
+
29
+ export function varGetName(v: Var): string { return v.Name; }
30
+ export function varGetType(v: Var): string { return v.Type; }
31
+ export function varGetIsMutable(v: Var): boolean { return v.IsMutable; }
32
+
33
+ // ===================================================================
34
+ // Expr nodes: tagged classes for each quotation expression kind
35
+ // ===================================================================
36
+
37
+ export class ExprValue {
38
+ readonly tag = "Value";
39
+ value: any;
40
+ type: string;
41
+ constructor(value: any, type: string) { this.value = value; this.type = type; }
42
+ toJSON() { return ["Value", this.value, this.type]; }
43
+ }
44
+
45
+ export class ExprVarExpr {
46
+ readonly tag = "Var";
47
+ var_: Var;
48
+ constructor(var_: Var) { this.var_ = var_; }
49
+ toJSON() { return ["Var", this.var_]; }
50
+ }
51
+
52
+ export class ExprLambda {
53
+ readonly tag = "Lambda";
54
+ var_: Var;
55
+ body: Expr;
56
+ constructor(var_: Var, body: Expr) { this.var_ = var_; this.body = body; }
57
+ toJSON() { return ["Lambda", this.var_, this.body]; }
58
+ }
59
+
60
+ export class ExprApplication {
61
+ readonly tag = "Application";
62
+ func: Expr;
63
+ arg: Expr;
64
+ constructor(func: Expr, arg: Expr) { this.func = func; this.arg = arg; }
65
+ toJSON() { return ["Application", this.func, this.arg]; }
66
+ }
67
+
68
+ export class ExprLet {
69
+ readonly tag = "Let";
70
+ var_: Var;
71
+ value: Expr;
72
+ body: Expr;
73
+ constructor(var_: Var, value: Expr, body: Expr) { this.var_ = var_; this.value = value; this.body = body; }
74
+ toJSON() { return ["Let", this.var_, this.value, this.body]; }
75
+ }
76
+
77
+ export class ExprIfThenElse {
78
+ readonly tag = "IfThenElse";
79
+ guard: Expr;
80
+ thenExpr: Expr;
81
+ elseExpr: Expr;
82
+ constructor(guard: Expr, thenExpr: Expr, elseExpr: Expr) { this.guard = guard; this.thenExpr = thenExpr; this.elseExpr = elseExpr; }
83
+ toJSON() { return ["IfThenElse", this.guard, this.thenExpr, this.elseExpr]; }
84
+ }
85
+
86
+ export class ExprCall {
87
+ readonly tag = "Call";
88
+ instance: Expr | null;
89
+ method: string;
90
+ args: Expr[];
91
+ constructor(instance: Expr | null, method: string, args: Expr[]) { this.instance = instance; this.method = method; this.args = args; }
92
+ toJSON() { return ["Call", this.instance, this.method, this.args]; }
93
+ }
94
+
95
+ export class ExprSequential {
96
+ readonly tag = "Sequential";
97
+ first: Expr;
98
+ second: Expr;
99
+ constructor(first: Expr, second: Expr) { this.first = first; this.second = second; }
100
+ toJSON() { return ["Sequential", this.first, this.second]; }
101
+ }
102
+
103
+ export class ExprNewTuple {
104
+ readonly tag = "NewTuple";
105
+ elements: Expr[];
106
+ constructor(elements: Expr[]) { this.elements = elements; }
107
+ toJSON() { return ["NewTuple", this.elements]; }
108
+ }
109
+
110
+ export class ExprNewUnion {
111
+ readonly tag = "NewUnion";
112
+ typeName: string;
113
+ unionTag: number;
114
+ fields: Expr[];
115
+ constructor(typeName: string, unionTag: number, fields: Expr[]) { this.typeName = typeName; this.unionTag = unionTag; this.fields = fields; }
116
+ toJSON() { return ["NewUnion", this.typeName, this.unionTag, this.fields]; }
117
+ }
118
+
119
+ export class ExprNewRecord {
120
+ readonly tag = "NewRecord";
121
+ fieldNames: string[];
122
+ values: Expr[];
123
+ constructor(fieldNames: string[], values: Expr[]) { this.fieldNames = fieldNames; this.values = values; }
124
+ toJSON() { return ["NewRecord", this.fieldNames, this.values]; }
125
+ }
126
+
127
+ export class ExprNewList {
128
+ readonly tag = "NewList";
129
+ head: Expr;
130
+ tail: Expr;
131
+ constructor(head: Expr, tail: Expr) { this.head = head; this.tail = tail; }
132
+ toJSON() { return ["NewList", this.head, this.tail]; }
133
+ }
134
+
135
+ export class ExprTupleGet {
136
+ readonly tag = "TupleGet";
137
+ expr: Expr;
138
+ index: number;
139
+ constructor(expr: Expr, index: number) { this.expr = expr; this.index = index; }
140
+ toJSON() { return ["TupleGet", this.expr, this.index]; }
141
+ }
142
+
143
+ export class ExprUnionTag {
144
+ readonly tag = "UnionTag";
145
+ expr: Expr;
146
+ constructor(expr: Expr) { this.expr = expr; }
147
+ toJSON() { return ["UnionTag", this.expr]; }
148
+ }
149
+
150
+ export class ExprUnionField {
151
+ readonly tag = "UnionField";
152
+ expr: Expr;
153
+ fieldIndex: number;
154
+ constructor(expr: Expr, fieldIndex: number) { this.expr = expr; this.fieldIndex = fieldIndex; }
155
+ toJSON() { return ["UnionField", this.expr, this.fieldIndex]; }
156
+ }
157
+
158
+ export class ExprFieldGet {
159
+ readonly tag = "FieldGet";
160
+ expr: Expr;
161
+ fieldName: string;
162
+ constructor(expr: Expr, fieldName: string) { this.expr = expr; this.fieldName = fieldName; }
163
+ toJSON() { return ["FieldGet", this.expr, this.fieldName]; }
164
+ }
165
+
166
+ export class ExprFieldSet {
167
+ readonly tag = "FieldSet";
168
+ expr: Expr;
169
+ fieldName: string;
170
+ value: Expr;
171
+ constructor(expr: Expr, fieldName: string, value: Expr) { this.expr = expr; this.fieldName = fieldName; this.value = value; }
172
+ toJSON() { return ["FieldSet", this.expr, this.fieldName, this.value]; }
173
+ }
174
+
175
+ export class ExprVarSet {
176
+ readonly tag = "VarSet";
177
+ target: Expr;
178
+ value: Expr;
179
+ constructor(target: Expr, value: Expr) { this.target = target; this.value = value; }
180
+ toJSON() { return ["VarSet", this.target, this.value]; }
181
+ }
182
+
183
+ export type Expr =
184
+ | ExprValue
185
+ | ExprVarExpr
186
+ | ExprLambda
187
+ | ExprApplication
188
+ | ExprLet
189
+ | ExprIfThenElse
190
+ | ExprCall
191
+ | ExprSequential
192
+ | ExprNewTuple
193
+ | ExprNewUnion
194
+ | ExprNewRecord
195
+ | ExprNewList
196
+ | ExprTupleGet
197
+ | ExprUnionTag
198
+ | ExprUnionField
199
+ | ExprFieldGet
200
+ | ExprFieldSet
201
+ | ExprVarSet;
202
+
203
+ // ===================================================================
204
+ // Constructors (called by QuotationEmitter.fs)
205
+ // ===================================================================
206
+
207
+ export function mkValue(value: any, type: string): ExprValue {
208
+ return new ExprValue(value, type);
209
+ }
210
+
211
+ export function mkVar(v: Var): ExprVarExpr {
212
+ return new ExprVarExpr(v);
213
+ }
214
+
215
+ export function mkLambda(v: Var, body: Expr): ExprLambda {
216
+ return new ExprLambda(v, body);
217
+ }
218
+
219
+ export function mkApplication(func: Expr, arg: Expr): ExprApplication {
220
+ return new ExprApplication(func, arg);
221
+ }
222
+
223
+ export function mkLet(v: Var, value: Expr, body: Expr): ExprLet {
224
+ return new ExprLet(v, value, body);
225
+ }
226
+
227
+ export function mkIfThenElse(guard: Expr, thenExpr: Expr, elseExpr: Expr): ExprIfThenElse {
228
+ return new ExprIfThenElse(guard, thenExpr, elseExpr);
229
+ }
230
+
231
+ export function mkCall(instance: Expr | null, method: string, args: Expr[]): ExprCall {
232
+ return new ExprCall(instance, method, args);
233
+ }
234
+
235
+ export function mkSequential(first: Expr, second: Expr): ExprSequential {
236
+ return new ExprSequential(first, second);
237
+ }
238
+
239
+ export function mkNewTuple(elements: Expr[]): ExprNewTuple {
240
+ return new ExprNewTuple(elements);
241
+ }
242
+
243
+ export function mkTupleGet(expr: Expr, index: number): ExprTupleGet {
244
+ return new ExprTupleGet(expr, index);
245
+ }
246
+
247
+ export function mkUnionTag(expr: Expr): ExprUnionTag {
248
+ return new ExprUnionTag(expr);
249
+ }
250
+
251
+ export function mkUnionField(expr: Expr, fieldIndex: number): ExprUnionField {
252
+ return new ExprUnionField(expr, fieldIndex);
253
+ }
254
+
255
+ export function mkFieldGet(expr: Expr, fieldName: string): ExprFieldGet {
256
+ return new ExprFieldGet(expr, fieldName);
257
+ }
258
+
259
+ export function mkFieldSet(expr: Expr, fieldName: string, value: Expr): ExprFieldSet {
260
+ return new ExprFieldSet(expr, fieldName, value);
261
+ }
262
+
263
+ export function mkVarSet(target: Expr, value: Expr): ExprVarSet {
264
+ return new ExprVarSet(target, value);
265
+ }
266
+
267
+ export function mkNewUnion(typeName: string, tag: number, fields: Expr[]): ExprNewUnion {
268
+ return new ExprNewUnion(typeName, tag, fields);
269
+ }
270
+
271
+ export function mkNewRecord(fieldNames: string[], values: Expr[]): ExprNewRecord {
272
+ return new ExprNewRecord(fieldNames, values);
273
+ }
274
+
275
+ export function mkNewList(head: Expr, tail: Expr): ExprNewList {
276
+ return new ExprNewList(head, tail);
277
+ }
278
+
279
+ // ===================================================================
280
+ // Accessors
281
+ // ===================================================================
282
+
283
+ export function getType(expr: Expr): string {
284
+ if (expr instanceof ExprValue) return expr.type;
285
+ if (expr instanceof ExprLambda) return expr.var_.Type;
286
+ return "obj";
287
+ }
288
+
289
+ // ===================================================================
290
+ // Pattern match helpers
291
+ // Returns undefined (no match) or a value/tuple (match), following
292
+ // Fable's option convention for active patterns.
293
+ // ===================================================================
294
+
295
+ export function isValue(expr: Expr): [any, string] | undefined {
296
+ if (expr instanceof ExprValue) return [expr.value, expr.type];
297
+ return undefined;
298
+ }
299
+
300
+ export function isVar(expr: Expr): Var | undefined {
301
+ if (expr instanceof ExprVarExpr) return expr.var_;
302
+ return undefined;
303
+ }
304
+
305
+ export function isLambda(expr: Expr): [Var, Expr] | undefined {
306
+ if (expr instanceof ExprLambda) return [expr.var_, expr.body];
307
+ return undefined;
308
+ }
309
+
310
+ export function isApplication(expr: Expr): [Expr, Expr] | undefined {
311
+ if (expr instanceof ExprApplication) return [expr.func, expr.arg];
312
+ return undefined;
313
+ }
314
+
315
+ export function isLet(expr: Expr): [Var, Expr, Expr] | undefined {
316
+ if (expr instanceof ExprLet) return [expr.var_, expr.value, expr.body];
317
+ return undefined;
318
+ }
319
+
320
+ export function isIfThenElse(expr: Expr): [Expr, Expr, Expr] | undefined {
321
+ if (expr instanceof ExprIfThenElse) return [expr.guard, expr.thenExpr, expr.elseExpr];
322
+ return undefined;
323
+ }
324
+
325
+ export function isCall(expr: Expr): [Expr | null, string, Expr[]] | undefined {
326
+ if (expr instanceof ExprCall) return [expr.instance, expr.method, expr.args];
327
+ return undefined;
328
+ }
329
+
330
+ export function isSequential(expr: Expr): [Expr, Expr] | undefined {
331
+ if (expr instanceof ExprSequential) return [expr.first, expr.second];
332
+ return undefined;
333
+ }
334
+
335
+ export function isNewTuple(expr: Expr): Expr[] | undefined {
336
+ if (expr instanceof ExprNewTuple) return expr.elements;
337
+ return undefined;
338
+ }
339
+
340
+ export function isNewUnionCase(expr: Expr): [string, Expr[]] | undefined {
341
+ if (expr instanceof ExprNewUnion) return [expr.typeName, expr.fields];
342
+ return undefined;
343
+ }
344
+
345
+ export function isNewRecord(expr: Expr): [string[], Expr[]] | undefined {
346
+ if (expr instanceof ExprNewRecord) return [expr.fieldNames, expr.values];
347
+ return undefined;
348
+ }
349
+
350
+ export function isTupleGet(expr: Expr): [Expr, number] | undefined {
351
+ if (expr instanceof ExprTupleGet) return [expr.expr, expr.index];
352
+ return undefined;
353
+ }
354
+
355
+ export function isFieldGet(expr: Expr): [Expr, string] | undefined {
356
+ if (expr instanceof ExprFieldGet) return [expr.expr, expr.fieldName];
357
+ return undefined;
358
+ }
359
+
360
+ // ===================================================================
361
+ // Evaluation
362
+ // ===================================================================
363
+
364
+ const OPERATORS: Record<string, (...args: any[]) => any> = {
365
+ "op_Addition": (a: any, b: any) => a + b,
366
+ "op_Subtraction": (a: any, b: any) => a - b,
367
+ "op_Multiply": (a: any, b: any) => a * b,
368
+ "op_Division": (a: any, b: any) => a / b,
369
+ "op_Modulus": (a: any, b: any) => a % b,
370
+ "op_Exponentiation": (a: any, b: any) => a ** b,
371
+ "op_UnaryNegation": (a: any) => -a,
372
+ "op_UnaryPlus": (a: any) => +a,
373
+ "op_LogicalNot": (a: any) => !a,
374
+ "op_BitwiseOr": (a: any, b: any) => a | b,
375
+ "op_BitwiseAnd": (a: any, b: any) => a & b,
376
+ "op_ExclusiveOr": (a: any, b: any) => a ^ b,
377
+ "op_LeftShift": (a: any, b: any) => a << b,
378
+ "op_RightShift": (a: any, b: any) => a >> b,
379
+ "op_Equality": (a: any, b: any) => a === b,
380
+ "op_Inequality": (a: any, b: any) => a !== b,
381
+ "op_LessThan": (a: any, b: any) => a < b,
382
+ "op_LessThanOrEqual": (a: any, b: any) => a <= b,
383
+ "op_GreaterThan": (a: any, b: any) => a > b,
384
+ "op_GreaterThanOrEqual": (a: any, b: any) => a >= b,
385
+ "op_BooleanAnd": (a: any, b: any) => a && b,
386
+ "op_BooleanOr": (a: any, b: any) => a || b,
387
+ };
388
+
389
+ export function evaluate(expr: Expr, env?: Map<string, any>): any {
390
+ if (env == null) env = new Map();
391
+
392
+ if (expr instanceof ExprValue) return expr.value;
393
+
394
+ if (expr instanceof ExprVarExpr) {
395
+ if (env.has(expr.var_.Name)) return env.get(expr.var_.Name);
396
+ throw new Error(`Unbound variable: ${expr.var_.Name}`);
397
+ }
398
+
399
+ if (expr instanceof ExprLambda) {
400
+ const capturedEnv = new Map(env);
401
+ return (arg: any) => {
402
+ const newEnv = new Map(capturedEnv);
403
+ newEnv.set(expr.var_.Name, arg);
404
+ return evaluate(expr.body, newEnv);
405
+ };
406
+ }
407
+
408
+ if (expr instanceof ExprApplication) {
409
+ return evaluate(expr.func, env)(evaluate(expr.arg, env));
410
+ }
411
+
412
+ if (expr instanceof ExprLet) {
413
+ const newEnv = new Map(env);
414
+ newEnv.set(expr.var_.Name, evaluate(expr.value, env));
415
+ return evaluate(expr.body, newEnv);
416
+ }
417
+
418
+ if (expr instanceof ExprIfThenElse) {
419
+ return evaluate(expr.guard, env) ? evaluate(expr.thenExpr, env) : evaluate(expr.elseExpr, env);
420
+ }
421
+
422
+ if (expr instanceof ExprSequential) {
423
+ evaluate(expr.first, env);
424
+ return evaluate(expr.second, env);
425
+ }
426
+
427
+ if (expr instanceof ExprNewTuple) {
428
+ return expr.elements.map(e => evaluate(e, env));
429
+ }
430
+
431
+ if (expr instanceof ExprCall) {
432
+ const evaluatedArgs = expr.args.map((a: any) => evaluate(a, env));
433
+ if (expr.method in OPERATORS) return OPERATORS[expr.method](...evaluatedArgs);
434
+ throw new Error(`Unknown method: ${expr.method}`);
435
+ }
436
+
437
+ if (expr instanceof ExprTupleGet) {
438
+ return evaluate(expr.expr, env)[expr.index];
439
+ }
440
+
441
+ if (expr instanceof ExprNewUnion) {
442
+ return [expr.unionTag, ...expr.fields.map((f: any) => evaluate(f, env))];
443
+ }
444
+
445
+ if (expr instanceof ExprNewRecord) {
446
+ const result: Record<string, any> = {};
447
+ for (let i = 0; i < expr.fieldNames.length; i++) {
448
+ result[expr.fieldNames[i]] = evaluate(expr.values[i], env);
449
+ }
450
+ return result;
451
+ }
452
+
453
+ if (expr instanceof ExprNewList) {
454
+ return [evaluate(expr.head, env), ...evaluate(expr.tail, env)];
455
+ }
456
+
457
+ if (expr instanceof ExprVarSet) {
458
+ if (expr.target instanceof ExprVarExpr) {
459
+ env.set(expr.target.var_.Name, evaluate(expr.value, env));
460
+ return undefined;
461
+ }
462
+ throw new Error("VarSet target must be a variable");
463
+ }
464
+
465
+ if (expr instanceof ExprFieldGet) {
466
+ const obj = evaluate(expr.expr, env);
467
+ return obj[expr.fieldName];
468
+ }
469
+
470
+ throw new Error(`Cannot evaluate expression: ${(expr as any).tag}`);
471
+ }
472
+
473
+ // ===================================================================
474
+ // FSharpExpr instance methods
475
+ // ===================================================================
476
+
477
+ const OP_SYMBOLS: Record<string, string> = {
478
+ "op_Addition": "+",
479
+ "op_Subtraction": "-",
480
+ "op_Multiply": "*",
481
+ "op_Division": "/",
482
+ "op_Modulus": "%",
483
+ "op_Exponentiation": "**",
484
+ "op_Equality": "=",
485
+ "op_Inequality": "<>",
486
+ "op_LessThan": "<",
487
+ "op_LessThanOrEqual": "<=",
488
+ "op_GreaterThan": ">",
489
+ "op_GreaterThanOrEqual": ">=",
490
+ "op_BooleanAnd": "&&",
491
+ "op_BooleanOr": "||",
492
+ "op_UnaryNegation": "-",
493
+ "op_LogicalNot": "not",
494
+ };
495
+
496
+ export function exprToString(expr: Expr): string {
497
+ if (expr instanceof ExprValue) {
498
+ if (expr.type === "string") return `"${expr.value}"`;
499
+ if (expr.type === "unit") return "()";
500
+ if (expr.type === "bool") return expr.value ? "true" : "false";
501
+ return String(expr.value);
502
+ }
503
+ if (expr instanceof ExprVarExpr) return expr.var_.Name;
504
+ if (expr instanceof ExprLambda) return `fun ${expr.var_.Name} -> ${exprToString(expr.body)}`;
505
+ if (expr instanceof ExprApplication) return `${exprToString(expr.func)} ${exprToString(expr.arg)}`;
506
+ if (expr instanceof ExprLet) return `let ${expr.var_.Name} = ${exprToString(expr.value)} in ${exprToString(expr.body)}`;
507
+ if (expr instanceof ExprIfThenElse) return `if ${exprToString(expr.guard)} then ${exprToString(expr.thenExpr)} else ${exprToString(expr.elseExpr)}`;
508
+ if (expr instanceof ExprCall) {
509
+ if (expr.method in OP_SYMBOLS && expr.args.length === 2) {
510
+ return `(${exprToString(expr.args[0])} ${OP_SYMBOLS[expr.method]} ${exprToString(expr.args[1])})`;
511
+ }
512
+ if (expr.method in OP_SYMBOLS && expr.args.length === 1) {
513
+ return `${OP_SYMBOLS[expr.method]}${exprToString(expr.args[0])}`;
514
+ }
515
+ return `${expr.method}(${expr.args.map(exprToString).join(", ")})`;
516
+ }
517
+ if (expr instanceof ExprSequential) return `${exprToString(expr.first)}; ${exprToString(expr.second)}`;
518
+ if (expr instanceof ExprNewTuple) return `(${expr.elements.map(exprToString).join(", ")})`;
519
+ if (expr instanceof ExprTupleGet) return `Item${expr.index + 1}(${exprToString(expr.expr)})`;
520
+ if (expr instanceof ExprFieldGet) return `${exprToString(expr.expr)}.${expr.fieldName}`;
521
+ return `<${(expr as any).tag}>`;
522
+ }
523
+
524
+ export function getFreeVars(expr: Expr): Var[] {
525
+ const free: Var[] = [];
526
+ const seen = new Set<string>();
527
+
528
+ function walk(e: Expr, bound: Set<string>): void {
529
+ if (e instanceof ExprVarExpr) {
530
+ if (!bound.has(e.var_.Name) && !seen.has(e.var_.Name)) {
531
+ free.push(e.var_);
532
+ seen.add(e.var_.Name);
533
+ }
534
+ } else if (e instanceof ExprLambda) {
535
+ walk(e.body, new Set([...bound, e.var_.Name]));
536
+ } else if (e instanceof ExprLet) {
537
+ walk(e.value, bound);
538
+ walk(e.body, new Set([...bound, e.var_.Name]));
539
+ } else if (e instanceof ExprApplication) {
540
+ walk(e.func, bound);
541
+ walk(e.arg, bound);
542
+ } else if (e instanceof ExprIfThenElse) {
543
+ walk(e.guard, bound);
544
+ walk(e.thenExpr, bound);
545
+ walk(e.elseExpr, bound);
546
+ } else if (e instanceof ExprCall) {
547
+ for (const a of e.args) walk(a, bound);
548
+ } else if (e instanceof ExprSequential) {
549
+ walk(e.first, bound);
550
+ walk(e.second, bound);
551
+ } else if (e instanceof ExprNewTuple) {
552
+ for (const el of e.elements) walk(el, bound);
553
+ } else if (e instanceof ExprTupleGet) {
554
+ walk(e.expr, bound);
555
+ } else if (e instanceof ExprNewUnion) {
556
+ for (const f of e.fields) walk(f, bound);
557
+ } else if (e instanceof ExprNewRecord) {
558
+ for (const v of e.values) walk(v, bound);
559
+ } else if (e instanceof ExprNewList) {
560
+ walk(e.head, bound);
561
+ walk(e.tail, bound);
562
+ } else if (e instanceof ExprUnionTag) {
563
+ walk(e.expr, bound);
564
+ } else if (e instanceof ExprUnionField) {
565
+ walk(e.expr, bound);
566
+ } else if (e instanceof ExprFieldGet) {
567
+ walk(e.expr, bound);
568
+ } else if (e instanceof ExprFieldSet) {
569
+ walk(e.expr, bound);
570
+ walk(e.value, bound);
571
+ } else if (e instanceof ExprVarSet) {
572
+ walk(e.target, bound);
573
+ walk(e.value, bound);
574
+ }
575
+ }
576
+
577
+ walk(expr, new Set());
578
+ return free;
579
+ }
580
+
581
+ export function substitute(expr: Expr, fn: (v: Var) => Expr | undefined): Expr {
582
+ function sub(e: Expr): Expr {
583
+ if (e instanceof ExprVarExpr) {
584
+ const result = fn(e.var_);
585
+ return result !== undefined ? result : e;
586
+ }
587
+ if (e instanceof ExprLambda) return new ExprLambda(e.var_, sub(e.body));
588
+ if (e instanceof ExprLet) return new ExprLet(e.var_, sub(e.value), sub(e.body));
589
+ if (e instanceof ExprApplication) return new ExprApplication(sub(e.func), sub(e.arg));
590
+ if (e instanceof ExprIfThenElse) return new ExprIfThenElse(sub(e.guard), sub(e.thenExpr), sub(e.elseExpr));
591
+ if (e instanceof ExprCall) {
592
+ const newInst = e.instance != null ? sub(e.instance) : e.instance;
593
+ return new ExprCall(newInst, e.method, e.args.map(sub));
594
+ }
595
+ if (e instanceof ExprSequential) return new ExprSequential(sub(e.first), sub(e.second));
596
+ if (e instanceof ExprNewTuple) return new ExprNewTuple(e.elements.map(sub));
597
+ if (e instanceof ExprTupleGet) return new ExprTupleGet(sub(e.expr), e.index);
598
+ if (e instanceof ExprNewUnion) return new ExprNewUnion(e.typeName, e.unionTag, e.fields.map(sub));
599
+ if (e instanceof ExprNewRecord) return new ExprNewRecord(e.fieldNames, e.values.map(sub));
600
+ if (e instanceof ExprNewList) return new ExprNewList(sub(e.head), sub(e.tail));
601
+ if (e instanceof ExprUnionTag) return new ExprUnionTag(sub(e.expr));
602
+ if (e instanceof ExprUnionField) return new ExprUnionField(sub(e.expr), e.fieldIndex);
603
+ if (e instanceof ExprFieldGet) return new ExprFieldGet(sub(e.expr), e.fieldName);
604
+ if (e instanceof ExprFieldSet) return new ExprFieldSet(sub(e.expr), e.fieldName, sub(e.value));
605
+ if (e instanceof ExprVarSet) return new ExprVarSet(sub(e.target), sub(e.value));
606
+ return e;
607
+ }
608
+ return sub(expr);
609
+ }
610
+
611
+ // ===================================================================
612
+ // JSON deserialization
613
+ // Reconstructs Expr/Var from the toJSON() array format.
614
+ // ===================================================================
615
+
616
+ function varFromJSON(json: any): Var {
617
+ return new Var(json.Name, json.Type, json.IsMutable);
618
+ }
619
+
620
+ export function exprFromJSON(json: any): Expr {
621
+ if (!Array.isArray(json)) return new ExprValue(json, typeof json);
622
+ const [tag, ...fields] = json;
623
+ switch (tag) {
624
+ case "Value": return new ExprValue(fields[0], fields[1]);
625
+ case "Var": return new ExprVarExpr(varFromJSON(fields[0]));
626
+ case "Lambda": return new ExprLambda(varFromJSON(fields[0]), exprFromJSON(fields[1]));
627
+ case "Application": return new ExprApplication(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
628
+ case "Let": return new ExprLet(varFromJSON(fields[0]), exprFromJSON(fields[1]), exprFromJSON(fields[2]));
629
+ case "IfThenElse": return new ExprIfThenElse(exprFromJSON(fields[0]), exprFromJSON(fields[1]), exprFromJSON(fields[2]));
630
+ case "Call": return new ExprCall(fields[0] != null ? exprFromJSON(fields[0]) : null, fields[1], (fields[2] as any[]).map(exprFromJSON));
631
+ case "Sequential": return new ExprSequential(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
632
+ case "NewTuple": return new ExprNewTuple((fields[0] as any[]).map(exprFromJSON));
633
+ case "TupleGet": return new ExprTupleGet(exprFromJSON(fields[0]), fields[1]);
634
+ case "NewUnion": return new ExprNewUnion(fields[0], fields[1], (fields[2] as any[]).map(exprFromJSON));
635
+ case "UnionTag": return new ExprUnionTag(exprFromJSON(fields[0]));
636
+ case "UnionField": return new ExprUnionField(exprFromJSON(fields[0]), fields[1]);
637
+ case "NewRecord": return new ExprNewRecord(fields[0], (fields[1] as any[]).map(exprFromJSON));
638
+ case "FieldGet": return new ExprFieldGet(exprFromJSON(fields[0]), fields[1]);
639
+ case "FieldSet": return new ExprFieldSet(exprFromJSON(fields[0]), fields[1], exprFromJSON(fields[2]));
640
+ case "VarSet": return new ExprVarSet(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
641
+ case "NewList": return new ExprNewList(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
642
+ default: return new ExprValue(json, "unknown");
643
+ }
644
+ }