goscript 0.0.21 → 0.0.23

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.
@@ -1,2114 +0,0 @@
1
- /**
2
- * GoSliceObject contains metadata for complex slice views
3
- */
4
- interface GoSliceObject<T> {
5
- backing: T[] // The backing array
6
- offset: number // Offset into the backing array
7
- length: number // Length of the slice
8
- capacity: number // Capacity of the slice
9
- }
10
-
11
- /**
12
- * SliceProxy is a proxy object for complex slices
13
- */
14
- export type SliceProxy<T> = T[] & {
15
- __meta__: GoSliceObject<T>
16
- }
17
-
18
- /**
19
- * Slice<T> is a union type that is either a plain array or a proxy
20
- * null represents the nil state.
21
- */
22
- export type Slice<T> = T[] | SliceProxy<T> | null
23
-
24
- export function asArray<T>(slice: Slice<T>): T[] {
25
- return slice as T[]
26
- }
27
-
28
- /**
29
- * isComplexSlice checks if a slice is a complex slice (has __meta__ property)
30
- */
31
- function isComplexSlice<T>(slice: Slice<T>): slice is SliceProxy<T> {
32
- return (
33
- slice !== null &&
34
- slice !== undefined &&
35
- '__meta__' in slice &&
36
- slice.__meta__ !== undefined
37
- )
38
- }
39
-
40
- /**
41
- * Creates a new slice with the specified length and capacity.
42
- * @param length The length of the slice.
43
- * @param capacity The capacity of the slice (optional).
44
- * @returns A new slice.
45
- */
46
- export const makeSlice = <T>(length: number, capacity?: number): Slice<T> => {
47
- if (capacity === undefined) {
48
- capacity = length
49
- }
50
-
51
- if (length < 0 || capacity < 0 || length > capacity) {
52
- throw new Error(
53
- `Invalid slice length (${length}) or capacity (${capacity})`,
54
- )
55
- }
56
-
57
- const arr = new Array<T>(length)
58
-
59
- // Always create a complex slice with metadata to preserve capacity information
60
- const proxy = arr as SliceProxy<T>
61
- proxy.__meta__ = {
62
- backing: new Array<T>(capacity),
63
- offset: 0,
64
- length: length,
65
- capacity: capacity,
66
- }
67
-
68
- for (let i = 0; i < length; i++) {
69
- proxy.__meta__.backing[i] = arr[i]
70
- }
71
-
72
- return proxy
73
- }
74
-
75
- /**
76
- * goSlice creates a slice from s[low:high:max]
77
- * Arguments mirror Go semantics; omitted indices are undefined.
78
- *
79
- * @param s The original slice
80
- * @param low Starting index (defaults to 0)
81
- * @param high Ending index (defaults to s.length)
82
- * @param max Capacity limit (defaults to original capacity)
83
- */
84
- export const goSlice = <T>(
85
- s: Slice<T>,
86
- low?: number,
87
- high?: number,
88
- max?: number,
89
- ): Slice<T> => {
90
- if (s === null || s === undefined) {
91
- throw new Error('Cannot slice nil')
92
- }
93
-
94
- const slen = len(s)
95
- low = low ?? 0
96
- high = high ?? slen
97
-
98
- if (low < 0 || high < low) {
99
- throw new Error(`Invalid slice indices: ${low}:${high}`)
100
- }
101
-
102
- // In Go, high can be up to capacity, not just length
103
- const scap = cap(s)
104
- if (high > scap) {
105
- throw new Error(`Slice index out of range: ${high} > ${scap}`)
106
- }
107
-
108
- if (
109
- Array.isArray(s) &&
110
- !isComplexSlice(s) &&
111
- low === 0 &&
112
- high === s.length &&
113
- max === undefined
114
- ) {
115
- return s
116
- }
117
-
118
- let backing: T[]
119
- let oldOffset = 0
120
- let oldCap = scap
121
-
122
- // Get the backing array and offset
123
- if (isComplexSlice(s)) {
124
- backing = s.__meta__.backing
125
- oldOffset = s.__meta__.offset
126
- oldCap = s.__meta__.capacity
127
- } else {
128
- backing = s as T[]
129
- }
130
-
131
- let newCap
132
- if (max !== undefined) {
133
- if (max < high) {
134
- throw new Error(`Invalid slice indices: ${low}:${high}:${max}`)
135
- }
136
- if (isComplexSlice(s) && max > oldOffset + oldCap) {
137
- throw new Error(
138
- `Slice index out of range: ${max} > ${oldOffset + oldCap}`,
139
- )
140
- }
141
- if (!isComplexSlice(s) && max > s.length) {
142
- throw new Error(`Slice index out of range: ${max} > ${s.length}`)
143
- }
144
- newCap = max - low
145
- } else {
146
- // For slices of slices, capacity should be the capacity of the original slice minus the low index
147
- if (isComplexSlice(s)) {
148
- newCap = oldCap - low
149
- } else {
150
- newCap = s.length - low
151
- }
152
- }
153
-
154
- const newLength = high - low
155
- const newOffset = oldOffset + low
156
-
157
- const target = {
158
- __meta__: {
159
- backing: backing,
160
- offset: newOffset,
161
- length: newLength,
162
- capacity: newCap,
163
- },
164
- }
165
-
166
- const handler = {
167
- get(target: any, prop: string | symbol): any {
168
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
169
- const index = Number(prop)
170
- if (index >= 0 && index < target.__meta__.length) {
171
- return target.__meta__.backing[target.__meta__.offset + index]
172
- }
173
- throw new Error(
174
- `Slice index out of range: ${index} >= ${target.__meta__.length}`,
175
- )
176
- }
177
-
178
- if (prop === 'length') {
179
- return target.__meta__.length
180
- }
181
-
182
- if (prop === '__meta__') {
183
- return target.__meta__
184
- }
185
-
186
- if (
187
- prop === 'slice' ||
188
- prop === 'map' ||
189
- prop === 'filter' ||
190
- prop === 'reduce' ||
191
- prop === 'forEach' ||
192
- prop === Symbol.iterator
193
- ) {
194
- const backingSlice = target.__meta__.backing.slice(
195
- target.__meta__.offset,
196
- target.__meta__.offset + target.__meta__.length,
197
- )
198
- return backingSlice[prop].bind(backingSlice)
199
- }
200
-
201
- return Reflect.get(target, prop)
202
- },
203
-
204
- set(target: any, prop: string | symbol, value: any): boolean {
205
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
206
- const index = Number(prop)
207
- if (index >= 0 && index < target.__meta__.length) {
208
- target.__meta__.backing[target.__meta__.offset + index] = value
209
- return true
210
- }
211
- if (
212
- index === target.__meta__.length &&
213
- target.__meta__.length < target.__meta__.capacity
214
- ) {
215
- target.__meta__.backing[target.__meta__.offset + index] = value
216
- target.__meta__.length++
217
- return true
218
- }
219
- throw new Error(
220
- `Slice index out of range: ${index} >= ${target.__meta__.length}`,
221
- )
222
- }
223
-
224
- if (prop === 'length' || prop === '__meta__') {
225
- return false
226
- }
227
-
228
- return Reflect.set(target, prop, value)
229
- },
230
- }
231
-
232
- return new Proxy(target, handler) as unknown as SliceProxy<T>
233
- }
234
-
235
- /**
236
- * Creates a new map (TypeScript Map).
237
- * @returns A new TypeScript Map.
238
- */
239
- export const makeMap = <K, V>(): Map<K, V> => {
240
- return new Map<K, V>()
241
- }
242
-
243
- /**
244
- * Converts a JavaScript array to a Go slice.
245
- * For multi-dimensional arrays, recursively converts nested arrays to slices.
246
- * @param arr The JavaScript array to convert
247
- * @param depth How many levels of nesting to convert (default: 1, use Infinity for all levels)
248
- * @returns A Go slice containing the same elements
249
- */
250
- export const arrayToSlice = <T>(
251
- arr: T[] | null | undefined,
252
- depth: number = 1,
253
- ): T[] => {
254
- if (arr == null) return [] as T[]
255
-
256
- if (arr.length === 0) return arr
257
-
258
- const target = {
259
- __meta__: {
260
- backing: arr,
261
- offset: 0,
262
- length: arr.length,
263
- capacity: arr.length,
264
- },
265
- }
266
-
267
- const handler = {
268
- get(target: any, prop: string | symbol): any {
269
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
270
- const index = Number(prop)
271
- if (index >= 0 && index < target.__meta__.length) {
272
- return target.__meta__.backing[target.__meta__.offset + index]
273
- }
274
- throw new Error(
275
- `Slice index out of range: ${index} >= ${target.__meta__.length}`,
276
- )
277
- }
278
-
279
- if (prop === 'length') {
280
- return target.__meta__.length
281
- }
282
-
283
- if (prop === '__meta__') {
284
- return target.__meta__
285
- }
286
-
287
- if (
288
- prop === 'slice' ||
289
- prop === 'map' ||
290
- prop === 'filter' ||
291
- prop === 'reduce' ||
292
- prop === 'forEach' ||
293
- prop === Symbol.iterator
294
- ) {
295
- const backingSlice = target.__meta__.backing.slice(
296
- target.__meta__.offset,
297
- target.__meta__.offset + target.__meta__.length,
298
- )
299
- return backingSlice[prop].bind(backingSlice)
300
- }
301
-
302
- return Reflect.get(target, prop)
303
- },
304
-
305
- set(target: any, prop: string | symbol, value: any): boolean {
306
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
307
- const index = Number(prop)
308
- if (index >= 0 && index < target.__meta__.length) {
309
- target.__meta__.backing[target.__meta__.offset + index] = value
310
- return true
311
- }
312
- if (
313
- index === target.__meta__.length &&
314
- target.__meta__.length < target.__meta__.capacity
315
- ) {
316
- target.__meta__.backing[target.__meta__.offset + index] = value
317
- target.__meta__.length++
318
- return true
319
- }
320
- throw new Error(
321
- `Slice index out of range: ${index} >= ${target.__meta__.length}`,
322
- )
323
- }
324
-
325
- if (prop === 'length' || prop === '__meta__') {
326
- return false
327
- }
328
-
329
- return Reflect.set(target, prop, value)
330
- },
331
- }
332
-
333
- // Recursively convert nested arrays if depth > 1
334
- if (depth > 1 && arr.length > 0) {
335
- for (let i = 0; i < arr.length; i++) {
336
- const item = arr[i]
337
- if (isComplexSlice(item as any)) {
338
- } else if (Array.isArray(item)) {
339
- arr[i] = arrayToSlice(item as any[], depth - 1) as any
340
- } else if (
341
- item &&
342
- typeof item === 'object' &&
343
- isComplexSlice(item as any)
344
- ) {
345
- // Preserve capacity information for complex slices
346
- }
347
- }
348
- }
349
-
350
- return new Proxy(target, handler) as unknown as SliceProxy<T>
351
- }
352
-
353
- /**
354
- * Returns the length of a collection (string, array, slice, map, or set).
355
- * @param obj The collection to get the length of.
356
- * @returns The length of the collection.
357
- */
358
- export const len = <T = unknown, V = unknown>(
359
- obj: string | Array<T> | Slice<T> | Map<T, V> | Set<T> | null | undefined,
360
- ): number => {
361
- if (obj === null || obj === undefined) {
362
- return 0
363
- }
364
-
365
- if (typeof obj === 'string') {
366
- return obj.length
367
- }
368
-
369
- if (obj instanceof Map || obj instanceof Set) {
370
- return obj.size
371
- }
372
-
373
- if (isComplexSlice(obj)) {
374
- return obj.__meta__.length
375
- }
376
-
377
- if (Array.isArray(obj)) {
378
- return obj.length
379
- }
380
-
381
- return 0 // Default fallback
382
- }
383
-
384
- /**
385
- * Returns the capacity of a slice.
386
- * @param obj The slice.
387
- * @returns The capacity of the slice.
388
- */
389
- export const cap = <T>(obj: Slice<T>): number => {
390
- if (obj === null || obj === undefined) {
391
- return 0
392
- }
393
-
394
- if (isComplexSlice(obj)) {
395
- return obj.__meta__.capacity
396
- }
397
-
398
- if (Array.isArray(obj)) {
399
- return obj.length
400
- }
401
-
402
- return 0
403
- }
404
-
405
- /**
406
- * Appends elements to a slice.
407
- * Note: In Go, append can return a new slice if the underlying array is reallocated.
408
- * This helper emulates that by returning the modified or new slice.
409
- * @param slice The slice to append to.
410
- * @param elements The elements to append.
411
- * @returns The modified or new slice.
412
- */
413
- export const append = <T>(slice: Slice<T>, ...elements: T[]): T[] => {
414
- if (slice === null || slice === undefined) {
415
- if (elements.length === 0) {
416
- return [] as T[]
417
- } else {
418
- return elements.slice(0) as T[]
419
- }
420
- }
421
-
422
- if (elements.length === 0) {
423
- return slice
424
- }
425
-
426
- const oldLen = len(slice)
427
- const oldCap = cap(slice)
428
- const newLen = oldLen + elements.length
429
-
430
- if (newLen <= oldCap) {
431
- if (isComplexSlice(slice)) {
432
- const offset = slice.__meta__.offset
433
- const backing = slice.__meta__.backing
434
-
435
- for (let i = 0; i < elements.length; i++) {
436
- backing[offset + oldLen + i] = elements[i]
437
- }
438
-
439
- const result = new Array(newLen) as SliceProxy<T>
440
-
441
- for (let i = 0; i < oldLen; i++) {
442
- result[i] = backing[offset + i]
443
- }
444
- for (let i = 0; i < elements.length; i++) {
445
- result[oldLen + i] = elements[i]
446
- }
447
-
448
- result.__meta__ = {
449
- backing: backing,
450
- offset: offset,
451
- length: newLen,
452
- capacity: oldCap,
453
- }
454
-
455
- return result
456
- } else {
457
- const result = new Array(newLen) as SliceProxy<T>
458
-
459
- for (let i = 0; i < oldLen; i++) {
460
- result[i] = slice[i]
461
- }
462
-
463
- for (let i = 0; i < elements.length; i++) {
464
- result[i + oldLen] = elements[i]
465
-
466
- if (i + oldLen < oldCap && Array.isArray(slice)) {
467
- slice[i + oldLen] = elements[i]
468
- }
469
- }
470
-
471
- result.__meta__ = {
472
- backing: slice as any,
473
- offset: 0,
474
- length: newLen,
475
- capacity: oldCap,
476
- }
477
-
478
- return result
479
- }
480
- } else {
481
- let newCap = oldCap
482
- if (newCap == 0) {
483
- newCap = elements.length
484
- } else {
485
- if (newCap < 1024) {
486
- newCap *= 2
487
- } else {
488
- newCap += newCap / 4
489
- }
490
-
491
- // Ensure the new capacity fits all the elements
492
- if (newCap < newLen) {
493
- newCap = newLen
494
- }
495
- }
496
-
497
- const newBacking = new Array<T>(newCap)
498
-
499
- if (isComplexSlice(slice)) {
500
- const offset = slice.__meta__.offset
501
- const backing = slice.__meta__.backing
502
-
503
- for (let i = 0; i < oldLen; i++) {
504
- newBacking[i] = backing[offset + i]
505
- }
506
- } else {
507
- for (let i = 0; i < oldLen; i++) {
508
- newBacking[i] = slice[i]
509
- }
510
- }
511
-
512
- for (let i = 0; i < elements.length; i++) {
513
- newBacking[oldLen + i] = elements[i]
514
- }
515
-
516
- if (newLen === newCap) {
517
- return newBacking.slice(0, newLen) as T[]
518
- }
519
-
520
- const result = new Array(newLen) as SliceProxy<T>
521
-
522
- for (let i = 0; i < newLen; i++) {
523
- result[i] = newBacking[i]
524
- }
525
-
526
- result.__meta__ = {
527
- backing: newBacking,
528
- offset: 0,
529
- length: newLen,
530
- capacity: newCap,
531
- }
532
-
533
- return result
534
- }
535
- }
536
-
537
- /**
538
- * Copies elements from src to dst.
539
- * @param dst The destination slice.
540
- * @param src The source slice.
541
- * @returns The number of elements copied.
542
- */
543
- export const copy = <T>(dst: Slice<T>, src: Slice<T>): number => {
544
- if (dst === null || src === null) {
545
- return 0
546
- }
547
-
548
- const dstLen = len(dst)
549
- const srcLen = len(src)
550
-
551
- const count = Math.min(dstLen, srcLen)
552
-
553
- if (count === 0) {
554
- return 0
555
- }
556
-
557
- if (isComplexSlice(dst)) {
558
- const dstOffset = dst.__meta__.offset
559
- const dstBacking = dst.__meta__.backing
560
-
561
- if (isComplexSlice(src)) {
562
- const srcOffset = src.__meta__.offset
563
- const srcBacking = src.__meta__.backing
564
-
565
- for (let i = 0; i < count; i++) {
566
- dstBacking[dstOffset + i] = srcBacking[srcOffset + i]
567
- dst[i] = srcBacking[srcOffset + i] // Update the proxy array
568
- }
569
- } else {
570
- for (let i = 0; i < count; i++) {
571
- dstBacking[dstOffset + i] = src[i]
572
- dst[i] = src[i] // Update the proxy array
573
- }
574
- }
575
- } else {
576
- if (isComplexSlice(src)) {
577
- const srcOffset = src.__meta__.offset
578
- const srcBacking = src.__meta__.backing
579
-
580
- for (let i = 0; i < count; i++) {
581
- dst[i] = srcBacking[srcOffset + i]
582
- }
583
- } else {
584
- for (let i = 0; i < count; i++) {
585
- dst[i] = src[i]
586
- }
587
- }
588
- }
589
-
590
- return count
591
- }
592
-
593
- /**
594
- * Represents the Go error type (interface).
595
- */
596
- export type GoError = {
597
- Error(): string
598
- } | null
599
-
600
- /**
601
- * Converts a string to an array of Unicode code points (runes).
602
- * @param str The input string.
603
- * @returns An array of numbers representing the Unicode code points.
604
- */
605
- export const stringToRunes = (str: string): number[] => {
606
- return Array.from(str).map((c) => c.codePointAt(0) || 0)
607
- }
608
-
609
- /**
610
- * Converts an array of Unicode code points (runes) to a string.
611
- * @param runes The input array of numbers representing Unicode code points.
612
- * @returns The resulting string.
613
- */
614
- export const runesToString = (runes: Slice<number>): string => {
615
- return runes?.length ? String.fromCharCode(...runes) : ''
616
- }
617
-
618
- /**
619
- * Converts a number to a byte (uint8) by truncating to the range 0-255.
620
- * Equivalent to Go's byte() conversion.
621
- * @param n The number to convert to a byte.
622
- * @returns The byte value (0-255).
623
- */
624
- export const byte = (n: number): number => {
625
- return n & 0xff // Bitwise AND with 255 ensures we get a value in the range 0-255
626
- }
627
-
628
- /** Box represents a Go variable which can be referred to by other variables.
629
- *
630
- * For example:
631
- * var myVariable int
632
- *
633
- */
634
- export type Box<T> = { value: T }
635
-
636
- /** Wrap a non-null T in a pointer‐box. */
637
- export function box<T>(v: T): Box<T> {
638
- // We create a new object wrapper for every box call to ensure
639
- // distinct pointer identity, crucial for pointer comparisons (p1 == p2).
640
- return { value: v }
641
- }
642
-
643
- /** Dereference a pointer‐box, throws on null → simulates Go panic. */
644
- export function unbox<T>(b: Box<T>): T {
645
- if (b === null) {
646
- throw new Error(
647
- 'runtime error: invalid memory address or nil pointer dereference',
648
- )
649
- }
650
- return b.value
651
- }
652
-
653
- /**
654
- * Gets a value from a map, with a default value if the key doesn't exist.
655
- * @param map The map to get from.
656
- * @param key The key to get.
657
- * @param defaultValue The default value to return if the key doesn't exist (defaults to 0).
658
- * @returns The value for the key, or the default value if the key doesn't exist.
659
- */
660
- export const mapGet = <K, V>(
661
- map: Map<K, V>,
662
- key: K,
663
- defaultValue: V | null = null,
664
- ): V | null => {
665
- return map.has(key) ? map.get(key)! : defaultValue
666
- }
667
-
668
- /**
669
- * Sets a value in a map.
670
- * @param map The map to set in.
671
- * @param key The key to set.
672
- * @param value The value to set.
673
- */
674
- export const mapSet = <K, V>(map: Map<K, V>, key: K, value: V): void => {
675
- map.set(key, value)
676
- }
677
-
678
- /**
679
- * Deletes a key from a map.
680
- * @param map The map to delete from.
681
- * @param key The key to delete.
682
- */
683
- export const deleteMapEntry = <K, V>(map: Map<K, V>, key: K): void => {
684
- map.delete(key)
685
- }
686
-
687
- /**
688
- * Checks if a key exists in a map.
689
- * @param map The map to check in.
690
- * @param key The key to check.
691
- * @returns True if the key exists, false otherwise.
692
- */
693
- export const mapHas = <K, V>(map: Map<K, V>, key: K): boolean => {
694
- return map.has(key)
695
- }
696
-
697
- /**
698
- * Represents the kinds of Go types that can be registered at runtime.
699
- */
700
- export enum TypeKind {
701
- Basic = 'basic',
702
- Interface = 'interface',
703
- Struct = 'struct',
704
- Map = 'map',
705
- Slice = 'slice',
706
- Array = 'array',
707
- Pointer = 'pointer',
708
- Function = 'function',
709
- Channel = 'channel',
710
- }
711
-
712
- /**
713
- * TypeInfo is used for runtime type checking.
714
- * Can be a registered type (from typeRegistry) or an ad-hoc type description.
715
- * When used as input to typeAssert, it can be a string (type name) or a structured description.
716
- */
717
-
718
- /**
719
- * Base type information shared by all type kinds
720
- */
721
- export interface BaseTypeInfo {
722
- name?: string
723
- kind: TypeKind
724
- zeroValue?: any
725
- }
726
-
727
- /**
728
- * Type information for struct types
729
- */
730
- export interface StructTypeInfo extends BaseTypeInfo {
731
- kind: TypeKind.Struct
732
- methods: Set<string>
733
- ctor?: new (...args: any[]) => any
734
- fields: Record<string, TypeInfo | string> // Field names and types for struct fields
735
- }
736
-
737
- /**
738
- * Type information for interface types
739
- */
740
- export interface InterfaceTypeInfo extends BaseTypeInfo {
741
- kind: TypeKind.Interface
742
- methods: Set<string>
743
- }
744
-
745
- /**
746
- * Type information for basic types (string, number, boolean)
747
- */
748
- export interface BasicTypeInfo extends BaseTypeInfo {
749
- kind: TypeKind.Basic
750
- }
751
-
752
- /**
753
- * Type information for map types
754
- */
755
- export interface MapTypeInfo extends BaseTypeInfo {
756
- kind: TypeKind.Map
757
- keyType?: string | TypeInfo
758
- elemType?: string | TypeInfo
759
- }
760
-
761
- /**
762
- * Type information for slice types
763
- */
764
- export interface SliceTypeInfo extends BaseTypeInfo {
765
- kind: TypeKind.Slice
766
- elemType?: string | TypeInfo
767
- }
768
-
769
- /**
770
- * Type information for array types
771
- */
772
- export interface ArrayTypeInfo extends BaseTypeInfo {
773
- kind: TypeKind.Array
774
- elemType?: string | TypeInfo
775
- length: number
776
- }
777
-
778
- /**
779
- * Type information for pointer types
780
- */
781
- export interface PointerTypeInfo extends BaseTypeInfo {
782
- kind: TypeKind.Pointer
783
- elemType?: string | TypeInfo
784
- }
785
-
786
- /**
787
- * Type information for function types
788
- */
789
- export interface FunctionTypeInfo extends BaseTypeInfo {
790
- kind: TypeKind.Function
791
- params?: (string | TypeInfo)[]
792
- results?: (string | TypeInfo)[]
793
- }
794
-
795
- /**
796
- * Type information for channel types
797
- */
798
- export interface ChannelTypeInfo extends BaseTypeInfo {
799
- kind: TypeKind.Channel
800
- elemType?: string | TypeInfo
801
- direction?: 'send' | 'receive' | 'both'
802
- }
803
-
804
- /**
805
- * Union type representing all possible TypeInfo variants
806
- */
807
- export type TypeInfo =
808
- | StructTypeInfo
809
- | InterfaceTypeInfo
810
- | BasicTypeInfo
811
- | MapTypeInfo
812
- | SliceTypeInfo
813
- | ArrayTypeInfo
814
- | PointerTypeInfo
815
- | FunctionTypeInfo
816
- | ChannelTypeInfo
817
-
818
- // Type guard functions for TypeInfo variants
819
- export function isStructTypeInfo(info: TypeInfo): info is StructTypeInfo {
820
- return info.kind === TypeKind.Struct
821
- }
822
-
823
- export function isInterfaceTypeInfo(info: TypeInfo): info is InterfaceTypeInfo {
824
- return info.kind === TypeKind.Interface
825
- }
826
-
827
- export function isBasicTypeInfo(info: TypeInfo): info is BasicTypeInfo {
828
- return info.kind === TypeKind.Basic
829
- }
830
-
831
- export function isMapTypeInfo(info: TypeInfo): info is MapTypeInfo {
832
- return info.kind === TypeKind.Map
833
- }
834
-
835
- export function isSliceTypeInfo(info: TypeInfo): info is SliceTypeInfo {
836
- return info.kind === TypeKind.Slice
837
- }
838
-
839
- export function isArrayTypeInfo(info: TypeInfo): info is ArrayTypeInfo {
840
- return info.kind === TypeKind.Array
841
- }
842
-
843
- export function isPointerTypeInfo(info: TypeInfo): info is PointerTypeInfo {
844
- return info.kind === TypeKind.Pointer
845
- }
846
-
847
- export function isFunctionTypeInfo(info: TypeInfo): info is FunctionTypeInfo {
848
- return info.kind === TypeKind.Function
849
- }
850
-
851
- export function isChannelTypeInfo(info: TypeInfo): info is ChannelTypeInfo {
852
- return info.kind === TypeKind.Channel
853
- }
854
-
855
- // Registry to store runtime type information
856
- const typeRegistry = new Map<string, TypeInfo>()
857
-
858
- /**
859
- * Registers a struct type with the runtime type system.
860
- *
861
- * @param name The name of the type.
862
- * @param zeroValue The zero value for the type.
863
- * @param methods Set of method names for the struct.
864
- * @param ctor Constructor for the struct.
865
- * @returns The struct type information object.
866
- */
867
- export const registerStructType = (
868
- name: string,
869
- zeroValue: any,
870
- methods: Set<string>,
871
- ctor: new (...args: any[]) => any,
872
- fields: Record<string, TypeInfo | string> = {},
873
- ): StructTypeInfo => {
874
- const typeInfo: StructTypeInfo = {
875
- name,
876
- kind: TypeKind.Struct,
877
- zeroValue,
878
- methods,
879
- ctor,
880
- fields,
881
- }
882
- typeRegistry.set(name, typeInfo)
883
- return typeInfo
884
- }
885
-
886
- /**
887
- * Registers an interface type with the runtime type system.
888
- *
889
- * @param name The name of the type.
890
- * @param zeroValue The zero value for the type (usually null).
891
- * @param methods Set of method names the interface requires.
892
- * @returns The interface type information object.
893
- */
894
- export const registerInterfaceType = (
895
- name: string,
896
- zeroValue: any,
897
- methods: Set<string>,
898
- ): InterfaceTypeInfo => {
899
- const typeInfo: InterfaceTypeInfo = {
900
- name,
901
- kind: TypeKind.Interface,
902
- zeroValue,
903
- methods,
904
- }
905
- typeRegistry.set(name, typeInfo)
906
- return typeInfo
907
- }
908
-
909
- /**
910
- * Represents the result of a type assertion.
911
- */
912
- export interface TypeAssertResult<T> {
913
- value: T
914
- ok: boolean
915
- }
916
-
917
- /**
918
- * Normalizes a type info to a structured TypeInfo object.
919
- *
920
- * @param info The type info or name.
921
- * @returns A normalized TypeInfo object.
922
- */
923
- function normalizeTypeInfo(info: string | TypeInfo): TypeInfo {
924
- if (typeof info === 'string') {
925
- const typeInfo = typeRegistry.get(info)
926
- if (typeInfo) {
927
- return typeInfo
928
- }
929
- return {
930
- kind: TypeKind.Basic,
931
- name: info,
932
- }
933
- }
934
-
935
- return info
936
- }
937
-
938
- /**
939
- * Validates that a map key matches the expected type info.
940
- *
941
- * @param key The key to validate
942
- * @param keyTypeInfo The normalized type info for the key
943
- * @returns True if the key matches the type info, false otherwise
944
- */
945
- function validateMapKey(key: any, keyTypeInfo: TypeInfo): boolean {
946
- if (keyTypeInfo.kind === TypeKind.Basic) {
947
- // For string keys
948
- if (keyTypeInfo.name === 'string') {
949
- return typeof key === 'string'
950
- } else if (
951
- keyTypeInfo.name === 'int' ||
952
- keyTypeInfo.name === 'float64' ||
953
- keyTypeInfo.name === 'number'
954
- ) {
955
- if (typeof key === 'string') {
956
- return /^-?\d+(\.\d+)?$/.test(key)
957
- } else {
958
- return typeof key === 'number'
959
- }
960
- }
961
- }
962
- return false
963
- }
964
-
965
- /**
966
- * Checks if a value matches a basic type info.
967
- *
968
- * @param value The value to check.
969
- * @param info The basic type info to match against.
970
- * @returns True if the value matches the basic type, false otherwise.
971
- */
972
- function matchesBasicType(value: any, info: TypeInfo): boolean {
973
- if (info.name === 'string') return typeof value === 'string'
974
- if (info.name === 'number' || info.name === 'int' || info.name === 'float64')
975
- return typeof value === 'number'
976
- if (info.name === 'boolean' || info.name === 'bool')
977
- return typeof value === 'boolean'
978
- return false
979
- }
980
-
981
- /**
982
- * Checks if a value matches a struct type info.
983
- *
984
- * @param value The value to check.
985
- * @param info The struct type info to match against.
986
- * @returns True if the value matches the struct type, false otherwise.
987
- */
988
- function matchesStructType(value: any, info: TypeInfo): boolean {
989
- if (!isStructTypeInfo(info)) return false
990
-
991
- // For structs, use instanceof with the constructor
992
- if (info.ctor && value instanceof info.ctor) {
993
- return true
994
- }
995
-
996
- if (info.methods && typeof value === 'object' && value !== null) {
997
- const allMethodsMatch = Array.from(info.methods).every(
998
- (method) => typeof value[method] === 'function',
999
- )
1000
- if (allMethodsMatch) {
1001
- return true
1002
- }
1003
- }
1004
-
1005
- if (typeof value === 'object' && value !== null) {
1006
- const fieldNames = Object.keys(info.fields || {})
1007
- const valueFields = Object.keys(value)
1008
-
1009
- const fieldsExist = fieldNames.every((field) => field in value)
1010
- const sameFieldCount = valueFields.length === fieldNames.length
1011
- const allFieldsInStruct = valueFields.every((field) =>
1012
- fieldNames.includes(field),
1013
- )
1014
-
1015
- if (fieldsExist && sameFieldCount && allFieldsInStruct) {
1016
- return Object.entries(info.fields).every(([fieldName, fieldType]) => {
1017
- return matchesType(
1018
- value[fieldName],
1019
- normalizeTypeInfo(fieldType as TypeInfo | string),
1020
- )
1021
- })
1022
- }
1023
-
1024
- return false
1025
- }
1026
-
1027
- return false
1028
- }
1029
-
1030
- /**
1031
- * Checks if a value matches an interface type info.
1032
- *
1033
- * @param value The value to check.
1034
- * @param info The interface type info to match against.
1035
- * @returns True if the value matches the interface type, false otherwise.
1036
- */
1037
- function matchesInterfaceType(value: any, info: TypeInfo): boolean {
1038
- // For interfaces, check if the value has all the required methods
1039
- if (
1040
- isInterfaceTypeInfo(info) &&
1041
- info.methods &&
1042
- typeof value === 'object' &&
1043
- value !== null
1044
- ) {
1045
- return Array.from(info.methods).every(
1046
- (method) => typeof (value as any)[method] === 'function',
1047
- )
1048
- }
1049
- return false
1050
- }
1051
-
1052
- /**
1053
- * Checks if a value matches a map type info.
1054
- *
1055
- * @param value The value to check.
1056
- * @param info The map type info to match against.
1057
- * @returns True if the value matches the map type, false otherwise.
1058
- */
1059
- function matchesMapType(value: any, info: TypeInfo): boolean {
1060
- if (typeof value !== 'object' || value === null) return false
1061
- if (!isMapTypeInfo(info)) return false
1062
-
1063
- if (info.keyType || info.elemType) {
1064
- let entries: [any, any][] = []
1065
-
1066
- if (value instanceof Map) {
1067
- entries = Array.from(value.entries())
1068
- } else {
1069
- entries = Object.entries(value)
1070
- }
1071
-
1072
- if (entries.length === 0) return true // Empty map matches any map type
1073
-
1074
- const sampleSize = Math.min(5, entries.length)
1075
- for (let i = 0; i < sampleSize; i++) {
1076
- const [k, v] = entries[i]
1077
-
1078
- if (info.keyType) {
1079
- if (
1080
- !validateMapKey(
1081
- k,
1082
- normalizeTypeInfo(info.keyType as string | TypeInfo),
1083
- )
1084
- ) {
1085
- return false
1086
- }
1087
- }
1088
-
1089
- if (
1090
- info.elemType &&
1091
- !matchesType(v, normalizeTypeInfo(info.elemType as string | TypeInfo))
1092
- ) {
1093
- return false
1094
- }
1095
- }
1096
- }
1097
-
1098
- return true
1099
- }
1100
-
1101
- /**
1102
- * Checks if a value matches an array or slice type info.
1103
- *
1104
- * @param value The value to check.
1105
- * @param info The array or slice type info to match against.
1106
- * @returns True if the value matches the array or slice type, false otherwise.
1107
- */
1108
- function matchesArrayOrSliceType(value: any, info: TypeInfo): boolean {
1109
- // For slices and arrays, check if the value is an array and sample element types
1110
- if (!Array.isArray(value)) return false
1111
- if (!isArrayTypeInfo(info) && !isSliceTypeInfo(info)) return false
1112
-
1113
- if (info.elemType) {
1114
- const arr = value as any[]
1115
- if (arr.length === 0) return true // Empty array matches any array type
1116
-
1117
- const sampleSize = Math.min(5, arr.length)
1118
- for (let i = 0; i < sampleSize; i++) {
1119
- if (
1120
- !matchesType(
1121
- arr[i],
1122
- normalizeTypeInfo(info.elemType as string | TypeInfo),
1123
- )
1124
- ) {
1125
- return false
1126
- }
1127
- }
1128
- }
1129
-
1130
- return true
1131
- }
1132
-
1133
- /**
1134
- * Checks if a value matches a pointer type info.
1135
- *
1136
- * @param value The value to check.
1137
- * @param info The pointer type info to match against.
1138
- * @returns True if the value matches the pointer type, false otherwise.
1139
- */
1140
- function matchesPointerType(value: any, info: TypeInfo): boolean {
1141
- // Allow null/undefined values to match pointer types to support nil pointer assertions
1142
- // This enables Go's nil pointer type assertions like `ptr, ok := i.(*SomeType)` to work correctly
1143
- if (value === null || value === undefined) {
1144
- return true
1145
- }
1146
-
1147
- // Check if the value is a Box (has a 'value' property)
1148
- if (typeof value !== 'object' || !('value' in value)) {
1149
- return false
1150
- }
1151
-
1152
- if (!isPointerTypeInfo(info)) return false
1153
-
1154
- if (info.elemType) {
1155
- const elemTypeInfo = normalizeTypeInfo(info.elemType as string | TypeInfo)
1156
- return matchesType(value.value, elemTypeInfo)
1157
- }
1158
-
1159
- return true
1160
- }
1161
-
1162
- /**
1163
- * Checks if a value matches a function type info.
1164
- *
1165
- * @param value The value to check.
1166
- * @param info The function type info to match against.
1167
- * @returns True if the value matches the function type, false otherwise.
1168
- */
1169
- function matchesFunctionType(value: any, info: FunctionTypeInfo): boolean {
1170
- // First check if the value is a function
1171
- if (typeof value !== 'function') {
1172
- return false
1173
- }
1174
-
1175
- // This is important for named function types
1176
- if (info.name && value.__goTypeName) {
1177
- return info.name === value.__goTypeName
1178
- }
1179
-
1180
- return true
1181
- }
1182
-
1183
- /**
1184
- * Checks if a value matches a channel type info.
1185
- *
1186
- * @param value The value to check.
1187
- * @param info The channel type info to match against.
1188
- * @returns True if the value matches the channel type, false otherwise.
1189
- */
1190
- function matchesChannelType(value: any, info: ChannelTypeInfo): boolean {
1191
- // First check if it's a channel or channel reference
1192
- if (typeof value !== 'object' || value === null) {
1193
- return false
1194
- }
1195
-
1196
- // If it's a ChannelRef, get the underlying channel
1197
- let channel = value
1198
- let valueDirection = 'both'
1199
-
1200
- if ('channel' in value && 'direction' in value) {
1201
- channel = value.channel
1202
- valueDirection = value.direction
1203
- }
1204
-
1205
- // Check if it has channel methods
1206
- if (
1207
- !('send' in channel) ||
1208
- !('receive' in channel) ||
1209
- !('close' in channel) ||
1210
- typeof channel.send !== 'function' ||
1211
- typeof channel.receive !== 'function' ||
1212
- typeof channel.close !== 'function'
1213
- ) {
1214
- return false
1215
- }
1216
-
1217
- if (info.elemType) {
1218
- if (
1219
- info.elemType === 'string' &&
1220
- 'zeroValue' in channel &&
1221
- channel.zeroValue !== ''
1222
- ) {
1223
- return false
1224
- }
1225
-
1226
- if (
1227
- info.elemType === 'number' &&
1228
- 'zeroValue' in channel &&
1229
- typeof channel.zeroValue !== 'number'
1230
- ) {
1231
- return false
1232
- }
1233
- }
1234
-
1235
- if (info.direction) {
1236
- return valueDirection === info.direction
1237
- }
1238
-
1239
- return true
1240
- }
1241
-
1242
- /**
1243
- * Checks if a value matches a type info.
1244
- *
1245
- * @param value The value to check.
1246
- * @param info The type info to match against.
1247
- * @returns True if the value matches the type info, false otherwise.
1248
- */
1249
- function matchesType(value: any, info: TypeInfo): boolean {
1250
- if (value === null || value === undefined) {
1251
- return false
1252
- }
1253
-
1254
- switch (info.kind) {
1255
- case TypeKind.Basic:
1256
- return matchesBasicType(value, info)
1257
-
1258
- case TypeKind.Struct:
1259
- return matchesStructType(value, info)
1260
-
1261
- case TypeKind.Interface:
1262
- return matchesInterfaceType(value, info)
1263
-
1264
- case TypeKind.Map:
1265
- return matchesMapType(value, info)
1266
-
1267
- case TypeKind.Slice:
1268
- case TypeKind.Array:
1269
- return matchesArrayOrSliceType(value, info)
1270
-
1271
- case TypeKind.Pointer:
1272
- return matchesPointerType(value, info)
1273
-
1274
- case TypeKind.Function:
1275
- return matchesFunctionType(value, info as FunctionTypeInfo)
1276
-
1277
- case TypeKind.Channel:
1278
- return matchesChannelType(value, info)
1279
-
1280
- default:
1281
- console.warn(
1282
- `Type matching for kind '${(info as TypeInfo).kind}' not implemented.`,
1283
- )
1284
- return false
1285
- }
1286
- }
1287
-
1288
- export function typeAssert<T>(
1289
- value: any,
1290
- typeInfo: string | TypeInfo,
1291
- ): TypeAssertResult<T> {
1292
- const normalizedType = normalizeTypeInfo(typeInfo)
1293
-
1294
- if (isPointerTypeInfo(normalizedType) && value === null) {
1295
- return { value: null as unknown as T, ok: true }
1296
- }
1297
-
1298
- if (
1299
- isStructTypeInfo(normalizedType) &&
1300
- normalizedType.methods &&
1301
- typeof value === 'object' &&
1302
- value !== null
1303
- ) {
1304
- // Check if the value implements all methods of the struct type
1305
- const allMethodsMatch = Array.from(normalizedType.methods).every(
1306
- (method) => typeof value[method] === 'function',
1307
- )
1308
-
1309
- const hasAnyMethod = Array.from(normalizedType.methods).some(
1310
- (method) => typeof value[method] === 'function',
1311
- )
1312
-
1313
- if (allMethodsMatch && hasAnyMethod && normalizedType.methods.size > 0) {
1314
- // For interface-to-concrete type assertions, we just need to check methods
1315
- return { value: value as T, ok: true }
1316
- }
1317
- }
1318
-
1319
- if (
1320
- isStructTypeInfo(normalizedType) &&
1321
- normalizedType.fields &&
1322
- typeof value === 'object' &&
1323
- value !== null
1324
- ) {
1325
- const fieldNames = Object.keys(normalizedType.fields)
1326
- const valueFields = Object.keys(value)
1327
-
1328
- // For struct type assertions, we need exact field matching
1329
- const structFieldsMatch =
1330
- fieldNames.length === valueFields.length &&
1331
- fieldNames.every((field: string) => field in value) &&
1332
- valueFields.every((field) => fieldNames.includes(field))
1333
-
1334
- if (structFieldsMatch) {
1335
- const typesMatch = Object.entries(normalizedType.fields).every(
1336
- ([fieldName, fieldType]) => {
1337
- return matchesType(
1338
- value[fieldName],
1339
- normalizeTypeInfo(fieldType as TypeInfo | string),
1340
- )
1341
- },
1342
- )
1343
-
1344
- return { value: value as T, ok: typesMatch }
1345
- } else {
1346
- return { value: null as unknown as T, ok: false }
1347
- }
1348
- }
1349
-
1350
- if (
1351
- isMapTypeInfo(normalizedType) &&
1352
- typeof value === 'object' &&
1353
- value !== null
1354
- ) {
1355
- if (normalizedType.keyType || normalizedType.elemType) {
1356
- let entries: [any, any][] = []
1357
-
1358
- if (value instanceof Map) {
1359
- entries = Array.from(value.entries())
1360
- } else {
1361
- entries = Object.entries(value)
1362
- }
1363
-
1364
- if (entries.length === 0) {
1365
- return { value: value as T, ok: true }
1366
- }
1367
-
1368
- const sampleSize = Math.min(5, entries.length)
1369
- for (let i = 0; i < sampleSize; i++) {
1370
- const [k, v] = entries[i]
1371
-
1372
- if (normalizedType.keyType) {
1373
- if (
1374
- !validateMapKey(
1375
- k,
1376
- normalizeTypeInfo(normalizedType.keyType as string | TypeInfo),
1377
- )
1378
- ) {
1379
- return { value: null as unknown as T, ok: false }
1380
- }
1381
- }
1382
-
1383
- if (normalizedType.elemType) {
1384
- const elemTypeInfo = normalizeTypeInfo(
1385
- normalizedType.elemType as string | TypeInfo,
1386
- )
1387
- if (!matchesType(v, elemTypeInfo)) {
1388
- return { value: null as unknown as T, ok: false }
1389
- }
1390
- }
1391
- }
1392
-
1393
- // If we get here, the map type assertion passes
1394
- return { value: value as T, ok: true }
1395
- }
1396
- }
1397
-
1398
- const matches = matchesType(value, normalizedType)
1399
-
1400
- if (matches) {
1401
- return { value: value as T, ok: true }
1402
- }
1403
-
1404
- // If we get here, the assertion failed
1405
- // For registered types, use the zero value from the registry
1406
- if (typeof typeInfo === 'string') {
1407
- const registeredType = typeRegistry.get(typeInfo)
1408
- if (registeredType && registeredType.zeroValue !== undefined) {
1409
- return { value: registeredType.zeroValue as T, ok: false }
1410
- }
1411
- } else if (normalizedType.zeroValue !== undefined) {
1412
- return { value: normalizedType.zeroValue as T, ok: false }
1413
- }
1414
-
1415
- return { value: null as unknown as T, ok: false }
1416
- }
1417
-
1418
- /**
1419
- * Represents the result of a channel receive operation with 'ok' value
1420
- */
1421
- export interface ChannelReceiveResult<T> {
1422
- value: T // Should be T | ZeroValue<T>
1423
- ok: boolean
1424
- }
1425
-
1426
- /**
1427
- * Represents a result from a select operation
1428
- */
1429
- export interface SelectResult<T> {
1430
- value: T // Should be T | ZeroValue<T>
1431
- ok: boolean
1432
- id: number
1433
- }
1434
-
1435
- /**
1436
- * Represents a Go channel in TypeScript.
1437
- * Supports asynchronous sending and receiving of values.
1438
- */
1439
- export interface Channel<T> {
1440
- /**
1441
- * Sends a value to the channel.
1442
- * Returns a promise that resolves when the value is accepted by the channel.
1443
- * @param value The value to send.
1444
- */
1445
- send(value: T): Promise<void>
1446
-
1447
- /**
1448
- * Receives a value from the channel.
1449
- * Returns a promise that resolves with the received value.
1450
- * If the channel is closed, it throws an error.
1451
- */
1452
- receive(): Promise<T>
1453
-
1454
- /**
1455
- * Receives a value from the channel along with a boolean indicating
1456
- * whether the channel is still open.
1457
- * Returns a promise that resolves with {value, ok}.
1458
- * - If channel is open and has data: {value: <data>, ok: true}
1459
- * - If channel is closed and empty: {value: <zero value>, ok: false}
1460
- * - If channel is closed but has remaining buffered data: {value: <data>, ok: true}
1461
- */
1462
- receiveWithOk(): Promise<ChannelReceiveResult<T>>
1463
-
1464
- /**
1465
- * Closes the channel.
1466
- * No more values can be sent to a closed channel.
1467
- * Receive operations on a closed channel return the zero value and ok=false.
1468
- */
1469
- close(): void
1470
-
1471
- /**
1472
- * Used in select statements to create a receive operation promise.
1473
- * @param id An identifier for this case in the select statement
1474
- * @returns Promise that resolves when this case is selected
1475
- */
1476
- selectReceive(id: number): Promise<SelectResult<T>>
1477
-
1478
- /**
1479
- * Used in select statements to create a send operation promise.
1480
- * @param value The value to send
1481
- * @param id An identifier for this case in the select statement
1482
- * @returns Promise that resolves when this case is selected
1483
- */
1484
- selectSend(value: T, id: number): Promise<SelectResult<boolean>>
1485
-
1486
- /**
1487
- * Checks if the channel has data ready to be received without blocking.
1488
- * Used for non-blocking select operations.
1489
- */
1490
- canReceiveNonBlocking(): boolean
1491
-
1492
- /**
1493
- * Checks if the channel can accept a send operation without blocking.
1494
- * Used for non-blocking select operations.
1495
- */
1496
- canSendNonBlocking(): boolean
1497
- }
1498
-
1499
- // A simple implementation of buffered channels
1500
- class BufferedChannel<T> implements Channel<T> {
1501
- private buffer: T[] = []
1502
- private closed: boolean = false
1503
- private capacity: number
1504
- public zeroValue: T // Made public for access by ChannelRef or for type inference
1505
-
1506
- // Senders queue: stores { value, resolve for send, reject for send }
1507
- private senders: Array<{
1508
- value: T
1509
- resolveSend: () => void
1510
- rejectSend: (e: Error) => void
1511
- }> = []
1512
-
1513
- // Receivers queue for receive(): stores { resolve for receive, reject for receive }
1514
- private receivers: Array<{
1515
- resolveReceive: (value: T) => void
1516
- rejectReceive: (e: Error) => void
1517
- }> = []
1518
-
1519
- // Receivers queue for receiveWithOk(): stores { resolve for receiveWithOk }
1520
- private receiversWithOk: Array<{
1521
- resolveReceive: (result: ChannelReceiveResult<T>) => void
1522
- }> = []
1523
-
1524
- constructor(capacity: number, zeroValue: T) {
1525
- if (capacity < 0) {
1526
- throw new Error('Channel capacity cannot be negative')
1527
- }
1528
- this.capacity = capacity
1529
- this.zeroValue = zeroValue
1530
- }
1531
-
1532
- async send(value: T): Promise<void> {
1533
- if (this.closed) {
1534
- throw new Error('send on closed channel')
1535
- }
1536
-
1537
- // Attempt to hand off to a waiting receiver (rendezvous)
1538
- if (this.receivers.length > 0) {
1539
- const receiverTask = this.receivers.shift()!
1540
- queueMicrotask(() => receiverTask.resolveReceive(value))
1541
- return
1542
- }
1543
- if (this.receiversWithOk.length > 0) {
1544
- const receiverTask = this.receiversWithOk.shift()!
1545
- queueMicrotask(() => receiverTask.resolveReceive({ value, ok: true }))
1546
- return
1547
- }
1548
-
1549
- // If no waiting receivers, try to buffer if space is available
1550
- if (this.buffer.length < this.capacity) {
1551
- this.buffer.push(value)
1552
- return
1553
- }
1554
-
1555
- // Buffer is full (or capacity is 0 and no receivers are waiting). Sender must block.
1556
- return new Promise<void>((resolve, reject) => {
1557
- this.senders.push({ value, resolveSend: resolve, rejectSend: reject })
1558
- })
1559
- }
1560
-
1561
- async receive(): Promise<T> {
1562
- // Attempt to get from buffer first
1563
- if (this.buffer.length > 0) {
1564
- const value = this.buffer.shift()!
1565
- // If a sender was waiting because the buffer was full, unblock it.
1566
- if (this.senders.length > 0) {
1567
- const senderTask = this.senders.shift()!
1568
- this.buffer.push(senderTask.value) // Sender's value now goes into buffer
1569
- queueMicrotask(() => senderTask.resolveSend()) // Unblock sender
1570
- }
1571
- return value
1572
- }
1573
-
1574
- // Buffer is empty.
1575
- // If channel is closed (and buffer is empty), subsequent receives panic.
1576
- if (this.closed) {
1577
- throw new Error('receive on closed channel')
1578
- }
1579
-
1580
- // Buffer is empty, channel is open.
1581
- // Attempt to rendezvous with a waiting sender.
1582
- if (this.senders.length > 0) {
1583
- const senderTask = this.senders.shift()!
1584
- queueMicrotask(() => senderTask.resolveSend()) // Unblock the sender
1585
- return senderTask.value // Return the value from sender
1586
- }
1587
-
1588
- // Buffer is empty, channel is open, no waiting senders. Receiver must block.
1589
- return new Promise<T>((resolve, reject) => {
1590
- this.receivers.push({ resolveReceive: resolve, rejectReceive: reject })
1591
- })
1592
- }
1593
-
1594
- async receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1595
- // Attempt to get from buffer first
1596
- if (this.buffer.length > 0) {
1597
- const value = this.buffer.shift()!
1598
- if (this.senders.length > 0) {
1599
- const senderTask = this.senders.shift()!
1600
- this.buffer.push(senderTask.value)
1601
- queueMicrotask(() => senderTask.resolveSend())
1602
- }
1603
- return { value, ok: true }
1604
- }
1605
-
1606
- // Buffer is empty.
1607
- // Attempt to rendezvous with a waiting sender.
1608
- if (this.senders.length > 0) {
1609
- const senderTask = this.senders.shift()!
1610
- queueMicrotask(() => senderTask.resolveSend())
1611
- return { value: senderTask.value, ok: true }
1612
- }
1613
-
1614
- // Buffer is empty, no waiting senders.
1615
- // If channel is closed, return zero value with ok: false.
1616
- if (this.closed) {
1617
- return { value: this.zeroValue, ok: false }
1618
- }
1619
-
1620
- // Buffer is empty, channel is open, no waiting senders. Receiver must block.
1621
- return new Promise<ChannelReceiveResult<T>>((resolve) => {
1622
- this.receiversWithOk.push({ resolveReceive: resolve })
1623
- })
1624
- }
1625
-
1626
- async selectReceive(id: number): Promise<SelectResult<T>> {
1627
- if (this.buffer.length > 0) {
1628
- const value = this.buffer.shift()!
1629
- if (this.senders.length > 0) {
1630
- const senderTask = this.senders.shift()!
1631
- this.buffer.push(senderTask.value)
1632
- queueMicrotask(() => senderTask.resolveSend())
1633
- }
1634
- return { value, ok: true, id }
1635
- }
1636
-
1637
- if (this.senders.length > 0) {
1638
- const senderTask = this.senders.shift()!
1639
- queueMicrotask(() => senderTask.resolveSend())
1640
- return { value: senderTask.value, ok: true, id }
1641
- }
1642
-
1643
- if (this.closed) {
1644
- return { value: this.zeroValue, ok: false, id }
1645
- }
1646
-
1647
- return new Promise<SelectResult<T>>((resolve) => {
1648
- this.receiversWithOk.push({
1649
- resolveReceive: (result: ChannelReceiveResult<T>) => {
1650
- resolve({ ...result, id })
1651
- },
1652
- })
1653
- })
1654
- }
1655
-
1656
- async selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1657
- if (this.closed) {
1658
- // A select case sending on a closed channel panics in Go.
1659
- // This will cause Promise.race in selectStatement to reject.
1660
- throw new Error('send on closed channel')
1661
- }
1662
-
1663
- if (this.receivers.length > 0) {
1664
- const receiverTask = this.receivers.shift()!
1665
- queueMicrotask(() => receiverTask.resolveReceive(value))
1666
- return { value: true, ok: true, id }
1667
- }
1668
- if (this.receiversWithOk.length > 0) {
1669
- const receiverTask = this.receiversWithOk.shift()!
1670
- queueMicrotask(() => receiverTask.resolveReceive({ value, ok: true }))
1671
- return { value: true, ok: true, id }
1672
- }
1673
-
1674
- if (this.buffer.length < this.capacity) {
1675
- this.buffer.push(value)
1676
- return { value: true, ok: true, id }
1677
- }
1678
-
1679
- return new Promise<SelectResult<boolean>>((resolve, reject) => {
1680
- this.senders.push({
1681
- value,
1682
- resolveSend: () => resolve({ value: true, ok: true, id }),
1683
- rejectSend: (e) => reject(e), // Propagate error if channel closes
1684
- })
1685
- })
1686
- }
1687
-
1688
- close(): void {
1689
- if (this.closed) {
1690
- throw new Error('close of closed channel')
1691
- }
1692
- this.closed = true
1693
-
1694
- const sendersToNotify = [...this.senders] // Shallow copy for iteration
1695
- this.senders = []
1696
- for (const senderTask of sendersToNotify) {
1697
- queueMicrotask(() =>
1698
- senderTask.rejectSend(new Error('send on closed channel')),
1699
- )
1700
- }
1701
-
1702
- const receiversToNotify = [...this.receivers]
1703
- this.receivers = []
1704
- for (const receiverTask of receiversToNotify) {
1705
- queueMicrotask(() =>
1706
- receiverTask.rejectReceive(new Error('receive on closed channel')),
1707
- )
1708
- }
1709
-
1710
- const receiversWithOkToNotify = [...this.receiversWithOk]
1711
- this.receiversWithOk = []
1712
- for (const receiverTask of receiversWithOkToNotify) {
1713
- queueMicrotask(() =>
1714
- receiverTask.resolveReceive({ value: this.zeroValue, ok: false }),
1715
- )
1716
- }
1717
- }
1718
-
1719
- canReceiveNonBlocking(): boolean {
1720
- return this.buffer.length > 0 || this.senders.length > 0 || this.closed
1721
- }
1722
-
1723
- canSendNonBlocking(): boolean {
1724
- if (this.closed) {
1725
- return true // Ready to panic
1726
- }
1727
- return (
1728
- this.buffer.length < this.capacity ||
1729
- this.receivers.length > 0 ||
1730
- this.receiversWithOk.length > 0
1731
- )
1732
- }
1733
- }
1734
-
1735
- /**
1736
- * Represents a reference to a channel with a specific direction.
1737
- */
1738
- export interface ChannelRef<T> {
1739
- /**
1740
- * The underlying channel
1741
- */
1742
- channel: Channel<T>
1743
-
1744
- /**
1745
- * The direction of this channel reference
1746
- */
1747
- direction: 'send' | 'receive' | 'both'
1748
-
1749
- // Channel methods
1750
- send(value: T): Promise<void>
1751
- receive(): Promise<T>
1752
- receiveWithOk(): Promise<ChannelReceiveResult<T>>
1753
- close(): void
1754
- canSendNonBlocking(): boolean
1755
- canReceiveNonBlocking(): boolean
1756
- selectSend(value: T, id: number): Promise<SelectResult<boolean>>
1757
- selectReceive(id: number): Promise<SelectResult<T>>
1758
- }
1759
-
1760
- /**
1761
- * A bidirectional channel reference.
1762
- */
1763
- export class BidirectionalChannelRef<T> implements ChannelRef<T> {
1764
- direction: 'both' = 'both'
1765
-
1766
- constructor(public channel: Channel<T>) {}
1767
-
1768
- // Delegate all methods to the underlying channel
1769
- send(value: T): Promise<void> {
1770
- return this.channel.send(value)
1771
- }
1772
-
1773
- receive(): Promise<T> {
1774
- return this.channel.receive()
1775
- }
1776
-
1777
- receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1778
- return this.channel.receiveWithOk()
1779
- }
1780
-
1781
- close(): void {
1782
- this.channel.close()
1783
- }
1784
-
1785
- canSendNonBlocking(): boolean {
1786
- return this.channel.canSendNonBlocking()
1787
- }
1788
-
1789
- canReceiveNonBlocking(): boolean {
1790
- return this.channel.canReceiveNonBlocking()
1791
- }
1792
-
1793
- selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1794
- return this.channel.selectSend(value, id)
1795
- }
1796
-
1797
- selectReceive(id: number): Promise<SelectResult<T>> {
1798
- return this.channel.selectReceive(id)
1799
- }
1800
- }
1801
-
1802
- /**
1803
- * A send-only channel reference.
1804
- */
1805
- export class SendOnlyChannelRef<T> implements ChannelRef<T> {
1806
- direction: 'send' = 'send'
1807
-
1808
- constructor(public channel: Channel<T>) {}
1809
-
1810
- // Allow send operations
1811
- send(value: T): Promise<void> {
1812
- return this.channel.send(value)
1813
- }
1814
-
1815
- // Allow close operations
1816
- close(): void {
1817
- this.channel.close()
1818
- }
1819
-
1820
- canSendNonBlocking(): boolean {
1821
- return this.channel.canSendNonBlocking()
1822
- }
1823
-
1824
- selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1825
- return this.channel.selectSend(value, id)
1826
- }
1827
-
1828
- // Disallow receive operations
1829
- receive(): Promise<T> {
1830
- throw new Error('Cannot receive from send-only channel')
1831
- }
1832
-
1833
- receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1834
- throw new Error('Cannot receive from send-only channel')
1835
- }
1836
-
1837
- canReceiveNonBlocking(): boolean {
1838
- return false
1839
- }
1840
-
1841
- selectReceive(id: number): Promise<SelectResult<T>> {
1842
- throw new Error('Cannot receive from send-only channel')
1843
- }
1844
- }
1845
-
1846
- /**
1847
- * A receive-only channel reference.
1848
- */
1849
- export class ReceiveOnlyChannelRef<T> implements ChannelRef<T> {
1850
- direction: 'receive' = 'receive'
1851
-
1852
- constructor(public channel: Channel<T>) {}
1853
-
1854
- // Allow receive operations
1855
- receive(): Promise<T> {
1856
- return this.channel.receive()
1857
- }
1858
-
1859
- receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1860
- return this.channel.receiveWithOk()
1861
- }
1862
-
1863
- canReceiveNonBlocking(): boolean {
1864
- return this.channel.canReceiveNonBlocking()
1865
- }
1866
-
1867
- selectReceive(id: number): Promise<SelectResult<T>> {
1868
- return this.channel.selectReceive(id)
1869
- }
1870
-
1871
- // Disallow send operations
1872
- send(value: T): Promise<void> {
1873
- throw new Error('Cannot send to receive-only channel')
1874
- }
1875
-
1876
- // Disallow close operations
1877
- close(): void {
1878
- throw new Error('Cannot close receive-only channel')
1879
- }
1880
-
1881
- canSendNonBlocking(): boolean {
1882
- return false
1883
- }
1884
-
1885
- selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1886
- throw new Error('Cannot send to receive-only channel')
1887
- }
1888
- }
1889
-
1890
- /**
1891
- * Creates a new channel reference with the specified direction.
1892
- */
1893
- export function makeChannelRef<T>(
1894
- channel: Channel<T>,
1895
- direction: 'send' | 'receive' | 'both',
1896
- ): ChannelRef<T> {
1897
- switch (direction) {
1898
- case 'send':
1899
- return new SendOnlyChannelRef<T>(channel)
1900
- case 'receive':
1901
- return new ReceiveOnlyChannelRef<T>(channel)
1902
- default: // 'both'
1903
- return new BidirectionalChannelRef<T>(channel)
1904
- }
1905
- }
1906
-
1907
- /**
1908
- * Represents a case in a select statement.
1909
- */
1910
- export interface SelectCase<T> {
1911
- id: number
1912
- isSend: boolean // true for send, false for receive
1913
- channel: Channel<any> | ChannelRef<any> | null // Allow null and ChannelRef
1914
- value?: any // Value to send for send cases
1915
- // Optional handlers for when this case is selected
1916
- onSelected?: (result: SelectResult<T>) => Promise<void>
1917
- }
1918
-
1919
- /**
1920
- * Helper for 'select' statements. Takes an array of select cases
1921
- * and resolves when one of them completes, following Go's select rules.
1922
- *
1923
- * @param cases Array of SelectCase objects
1924
- * @param hasDefault Whether there is a default case
1925
- * @returns A promise that resolves with the result of the selected case
1926
- */
1927
- export async function selectStatement<T>(
1928
- cases: SelectCase<T>[],
1929
- hasDefault: boolean = false,
1930
- ): Promise<void> {
1931
- if (cases.length === 0 && !hasDefault) {
1932
- // Go spec: If there are no cases, the select statement blocks forever.
1933
- // Emulate blocking forever with a promise that never resolves.
1934
- return new Promise<void>(() => {}) // Promise never resolves
1935
- }
1936
-
1937
- // 1. Check for ready (non-blocking) operations
1938
- const readyCases: SelectCase<T>[] = []
1939
- for (const caseObj of cases) {
1940
- if (caseObj.id === -1) {
1941
- // Skip default case in this check
1942
- continue
1943
- }
1944
- // Add check for channel existence
1945
- if (caseObj.channel) {
1946
- if (caseObj.isSend && caseObj.channel.canSendNonBlocking()) {
1947
- readyCases.push(caseObj)
1948
- } else if (!caseObj.isSend && caseObj.channel.canReceiveNonBlocking()) {
1949
- readyCases.push(caseObj)
1950
- }
1951
- }
1952
- }
1953
-
1954
- if (readyCases.length > 0) {
1955
- // If one or more cases are ready, choose one pseudo-randomly
1956
- const selectedCase =
1957
- readyCases[Math.floor(Math.random() * readyCases.length)]
1958
-
1959
- // Execute the selected operation and its onSelected handler
1960
- // Add check for channel existence
1961
- if (selectedCase.channel) {
1962
- if (selectedCase.isSend) {
1963
- const result = await selectedCase.channel.selectSend(
1964
- selectedCase.value,
1965
- selectedCase.id,
1966
- )
1967
- if (selectedCase.onSelected) {
1968
- await selectedCase.onSelected(result as SelectResult<T>) // Await the handler
1969
- }
1970
- } else {
1971
- const result = await selectedCase.channel.selectReceive(selectedCase.id)
1972
- if (selectedCase.onSelected) {
1973
- await selectedCase.onSelected(result) // Await the handler
1974
- }
1975
- }
1976
- } else {
1977
- // This case should ideally not happen if channel is required for non-default cases
1978
- console.error('Selected case without a channel:', selectedCase)
1979
- }
1980
- return // Return after executing a ready case
1981
- }
1982
-
1983
- // 2. If no operations are ready and there's a default case, select default
1984
- if (hasDefault) {
1985
- // Find the default case (it will have id -1)
1986
- const defaultCase = cases.find((c) => c.id === -1)
1987
- if (defaultCase && defaultCase.onSelected) {
1988
- // Execute the onSelected handler for the default case
1989
- await defaultCase.onSelected({
1990
- value: undefined,
1991
- ok: false,
1992
- id: -1,
1993
- } as SelectResult<T>) // Await the handler
1994
- }
1995
- return // Return after executing the default case
1996
- }
1997
-
1998
- // 3. If no operations are ready and no default case, block until one is ready
1999
- // Use Promise.race on the blocking promises
2000
- const blockingPromises = cases
2001
- .filter((c) => c.id !== -1)
2002
- .map((caseObj) => {
2003
- // Exclude default case
2004
- // Add check for channel existence (though it should always exist here)
2005
- if (caseObj.channel) {
2006
- if (caseObj.isSend) {
2007
- return caseObj.channel.selectSend(caseObj.value, caseObj.id)
2008
- } else {
2009
- return caseObj.channel.selectReceive(caseObj.id)
2010
- }
2011
- }
2012
- // Return a promise that never resolves if channel is somehow missing
2013
- return new Promise<SelectResult<any>>(() => {})
2014
- })
2015
-
2016
- const result = await Promise.race(blockingPromises)
2017
- // Execute onSelected handler for the selected case
2018
- const selectedCase = cases.find((c) => c.id === result.id)
2019
- if (selectedCase && selectedCase.onSelected) {
2020
- await selectedCase.onSelected(result) // Await the handler
2021
- }
2022
- // No explicit return needed here, as the function will implicitly return after the await
2023
- }
2024
-
2025
- /**
2026
- * Creates a new channel with the specified buffer size and zero value.
2027
- * @param bufferSize The size of the channel buffer. If 0, creates an unbuffered channel.
2028
- * @param zeroValue The zero value for the channel's element type.
2029
- * @param direction Optional direction for the channel. Default is 'both' (bidirectional).
2030
- * @returns A new channel instance or channel reference.
2031
- */
2032
- export const makeChannel = <T>(
2033
- bufferSize: number,
2034
- zeroValue: T,
2035
- direction: 'send' | 'receive' | 'both' = 'both',
2036
- ): Channel<T> | ChannelRef<T> => {
2037
- const channel = new BufferedChannel<T>(bufferSize, zeroValue)
2038
-
2039
- // Wrap the channel with the appropriate ChannelRef based on direction
2040
- if (direction === 'send') {
2041
- return new SendOnlyChannelRef<T>(channel) as ChannelRef<T>
2042
- } else if (direction === 'receive') {
2043
- return new ReceiveOnlyChannelRef<T>(channel) as ChannelRef<T>
2044
- } else {
2045
- return channel
2046
- }
2047
- }
2048
-
2049
- /**
2050
- * DisposableStack manages synchronous disposable resources, mimicking Go's defer behavior.
2051
- * Functions added via `defer` are executed in LIFO order when the stack is disposed.
2052
- * Implements the `Disposable` interface for use with `using` declarations.
2053
- */
2054
- export class DisposableStack implements Disposable {
2055
- private stack: (() => void)[] = []
2056
-
2057
- /**
2058
- * Adds a function to be executed when the stack is disposed.
2059
- * @param fn The function to defer.
2060
- */
2061
- defer(fn: () => void): void {
2062
- this.stack.push(fn)
2063
- }
2064
-
2065
- /**
2066
- * Disposes of the resources in the stack by executing the deferred functions
2067
- * in Last-In, First-Out (LIFO) order.
2068
- * If a deferred function throws an error, disposal stops, and the error is rethrown,
2069
- * similar to Go's panic behavior during defer execution.
2070
- */
2071
- [Symbol.dispose](): void {
2072
- // Emulate Go: if a deferred throws, stop and rethrow
2073
- while (this.stack.length) {
2074
- const fn = this.stack.pop()!
2075
- fn()
2076
- }
2077
- }
2078
- }
2079
-
2080
- /**
2081
- * AsyncDisposableStack manages asynchronous disposable resources, mimicking Go's defer behavior.
2082
- * Functions added via `defer` are executed sequentially in LIFO order when the stack is disposed.
2083
- * Implements the `AsyncDisposable` interface for use with `await using` declarations.
2084
- */
2085
- export class AsyncDisposableStack implements AsyncDisposable {
2086
- private stack: (() => Promise<void> | void)[] = []
2087
-
2088
- /**
2089
- * Adds a synchronous or asynchronous function to be executed when the stack is disposed.
2090
- * @param fn The function to defer. Can return void or a Promise<void>.
2091
- */
2092
- defer(fn: () => Promise<void> | void): void {
2093
- this.stack.push(fn)
2094
- }
2095
-
2096
- /**
2097
- * Asynchronously disposes of the resources in the stack by executing the deferred functions
2098
- * sequentially in Last-In, First-Out (LIFO) order. It awaits each function if it returns a promise.
2099
- */
2100
- async [Symbol.asyncDispose](): Promise<void> {
2101
- // Execute in LIFO order, awaiting each potentially async function
2102
- for (let i = this.stack.length - 1; i >= 0; --i) {
2103
- await this.stack[i]()
2104
- }
2105
- }
2106
- }
2107
-
2108
- /**
2109
- * Implementation of Go's built-in println function
2110
- * @param args Arguments to print
2111
- */
2112
- export function println(...args: any[]): void {
2113
- console.log(...args)
2114
- }