goscript 0.0.22 → 0.0.24
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/README.md +1 -1
- package/cmd/goscript/cmd_compile.go +3 -3
- package/compiler/analysis.go +302 -182
- package/compiler/analysis_test.go +220 -0
- package/compiler/assignment.go +42 -43
- package/compiler/builtin_test.go +102 -0
- package/compiler/compiler.go +117 -29
- package/compiler/compiler_test.go +36 -8
- package/compiler/composite-lit.go +133 -53
- package/compiler/config.go +7 -3
- package/compiler/config_test.go +6 -33
- package/compiler/decl.go +36 -0
- package/compiler/expr-call.go +116 -60
- package/compiler/expr-selector.go +88 -43
- package/compiler/expr-star.go +57 -65
- package/compiler/expr-type.go +132 -5
- package/compiler/expr-value.go +8 -38
- package/compiler/expr.go +326 -30
- package/compiler/field.go +3 -3
- package/compiler/lit.go +34 -2
- package/compiler/primitive.go +19 -12
- package/compiler/spec-struct.go +140 -9
- package/compiler/spec-value.go +119 -41
- package/compiler/spec.go +21 -6
- package/compiler/stmt-assign.go +65 -3
- package/compiler/stmt-for.go +11 -0
- package/compiler/stmt-range.go +119 -11
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +175 -238
- package/compiler/type-assert.go +125 -379
- package/compiler/type.go +216 -129
- package/dist/gs/builtin/builtin.js +37 -0
- package/dist/gs/builtin/builtin.js.map +1 -0
- package/dist/gs/builtin/channel.js +471 -0
- package/dist/gs/builtin/channel.js.map +1 -0
- package/dist/gs/builtin/defer.js +54 -0
- package/dist/gs/builtin/defer.js.map +1 -0
- package/dist/gs/builtin/io.js +15 -0
- package/dist/gs/builtin/io.js.map +1 -0
- package/dist/gs/builtin/map.js +44 -0
- package/dist/gs/builtin/map.js.map +1 -0
- package/dist/gs/builtin/slice.js +799 -0
- package/dist/gs/builtin/slice.js.map +1 -0
- package/dist/gs/builtin/type.js +745 -0
- package/dist/gs/builtin/type.js.map +1 -0
- package/dist/gs/builtin/varRef.js +14 -0
- package/dist/gs/builtin/varRef.js.map +1 -0
- package/dist/gs/context/context.js +55 -0
- package/dist/gs/context/context.js.map +1 -0
- package/dist/gs/context/index.js +2 -0
- package/dist/gs/context/index.js.map +1 -0
- package/dist/gs/runtime/index.js +2 -0
- package/dist/gs/runtime/index.js.map +1 -0
- package/dist/gs/runtime/runtime.js +158 -0
- package/dist/gs/runtime/runtime.js.map +1 -0
- package/dist/gs/time/index.js +2 -0
- package/dist/gs/time/index.js.map +1 -0
- package/dist/gs/time/time.js +115 -0
- package/dist/gs/time/time.js.map +1 -0
- package/package.json +7 -6
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2379
- package/dist/builtin/builtin.d.ts +0 -513
- package/dist/builtin/builtin.js +0 -1686
- package/dist/builtin/builtin.js.map +0 -1
package/builtin/builtin.ts
DELETED
|
@@ -1,2379 +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
|
-
* Represents an argument or a return value of a method.
|
|
729
|
-
*/
|
|
730
|
-
export interface MethodArg {
|
|
731
|
-
name?: string; // Name of the argument/return value, if available
|
|
732
|
-
type: TypeInfo | string; // TypeInfo object or string name of the type
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Represents the signature of a method, including its name, arguments, and return types.
|
|
737
|
-
*/
|
|
738
|
-
export interface MethodSignature {
|
|
739
|
-
name: string;
|
|
740
|
-
args: MethodArg[];
|
|
741
|
-
returns: MethodArg[];
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* Type information for struct types
|
|
746
|
-
*/
|
|
747
|
-
export interface StructTypeInfo extends BaseTypeInfo {
|
|
748
|
-
kind: TypeKind.Struct
|
|
749
|
-
methods: MethodSignature[] // Array of method signatures
|
|
750
|
-
ctor?: new (...args: any[]) => any
|
|
751
|
-
fields: Record<string, TypeInfo | string> // Field names and types for struct fields
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Type information for interface types
|
|
756
|
-
*/
|
|
757
|
-
export interface InterfaceTypeInfo extends BaseTypeInfo {
|
|
758
|
-
kind: TypeKind.Interface
|
|
759
|
-
methods: MethodSignature[] // Array of method signatures
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* Type information for basic types (string, number, boolean)
|
|
764
|
-
*/
|
|
765
|
-
export interface BasicTypeInfo extends BaseTypeInfo {
|
|
766
|
-
kind: TypeKind.Basic
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/**
|
|
770
|
-
* Type information for map types
|
|
771
|
-
*/
|
|
772
|
-
export interface MapTypeInfo extends BaseTypeInfo {
|
|
773
|
-
kind: TypeKind.Map
|
|
774
|
-
keyType?: string | TypeInfo
|
|
775
|
-
elemType?: string | TypeInfo
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Type information for slice types
|
|
780
|
-
*/
|
|
781
|
-
export interface SliceTypeInfo extends BaseTypeInfo {
|
|
782
|
-
kind: TypeKind.Slice
|
|
783
|
-
elemType?: string | TypeInfo
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Type information for array types
|
|
788
|
-
*/
|
|
789
|
-
export interface ArrayTypeInfo extends BaseTypeInfo {
|
|
790
|
-
kind: TypeKind.Array
|
|
791
|
-
elemType?: string | TypeInfo
|
|
792
|
-
length: number
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/**
|
|
796
|
-
* Type information for pointer types
|
|
797
|
-
*/
|
|
798
|
-
export interface PointerTypeInfo extends BaseTypeInfo {
|
|
799
|
-
kind: TypeKind.Pointer
|
|
800
|
-
elemType?: string | TypeInfo
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* Type information for function types
|
|
805
|
-
*/
|
|
806
|
-
export interface FunctionTypeInfo extends BaseTypeInfo {
|
|
807
|
-
kind: TypeKind.Function
|
|
808
|
-
params?: (string | TypeInfo)[]
|
|
809
|
-
results?: (string | TypeInfo)[]
|
|
810
|
-
isVariadic?: boolean // True if the function is variadic (e.g., ...T)
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Type information for channel types
|
|
815
|
-
*/
|
|
816
|
-
export interface ChannelTypeInfo extends BaseTypeInfo {
|
|
817
|
-
kind: TypeKind.Channel
|
|
818
|
-
elemType?: string | TypeInfo
|
|
819
|
-
direction?: 'send' | 'receive' | 'both'
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Union type representing all possible TypeInfo variants
|
|
824
|
-
*/
|
|
825
|
-
export type TypeInfo =
|
|
826
|
-
| StructTypeInfo
|
|
827
|
-
| InterfaceTypeInfo
|
|
828
|
-
| BasicTypeInfo
|
|
829
|
-
| MapTypeInfo
|
|
830
|
-
| SliceTypeInfo
|
|
831
|
-
| ArrayTypeInfo
|
|
832
|
-
| PointerTypeInfo
|
|
833
|
-
| FunctionTypeInfo
|
|
834
|
-
| ChannelTypeInfo
|
|
835
|
-
|
|
836
|
-
// Type guard functions for TypeInfo variants
|
|
837
|
-
export function isStructTypeInfo(info: TypeInfo): info is StructTypeInfo {
|
|
838
|
-
return info.kind === TypeKind.Struct
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
export function isInterfaceTypeInfo(info: TypeInfo): info is InterfaceTypeInfo {
|
|
842
|
-
return info.kind === TypeKind.Interface
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
export function isBasicTypeInfo(info: TypeInfo): info is BasicTypeInfo {
|
|
846
|
-
return info.kind === TypeKind.Basic
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
export function isMapTypeInfo(info: TypeInfo): info is MapTypeInfo {
|
|
850
|
-
return info.kind === TypeKind.Map
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
export function isSliceTypeInfo(info: TypeInfo): info is SliceTypeInfo {
|
|
854
|
-
return info.kind === TypeKind.Slice
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
export function isArrayTypeInfo(info: TypeInfo): info is ArrayTypeInfo {
|
|
858
|
-
return info.kind === TypeKind.Array
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
export function isPointerTypeInfo(info: TypeInfo): info is PointerTypeInfo {
|
|
862
|
-
return info.kind === TypeKind.Pointer
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
export function isFunctionTypeInfo(info: TypeInfo): info is FunctionTypeInfo {
|
|
866
|
-
return info.kind === TypeKind.Function
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
export function isChannelTypeInfo(info: TypeInfo): info is ChannelTypeInfo {
|
|
870
|
-
return info.kind === TypeKind.Channel
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Registry to store runtime type information
|
|
874
|
-
const typeRegistry = new Map<string, TypeInfo>()
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Registers a struct type with the runtime type system.
|
|
878
|
-
*
|
|
879
|
-
* @param name The name of the type.
|
|
880
|
-
* @param zeroValue The zero value for the type.
|
|
881
|
-
* @param methods Array of method signatures for the struct.
|
|
882
|
-
* @param ctor Constructor for the struct.
|
|
883
|
-
* @param fields Record of field names and their types.
|
|
884
|
-
* @returns The struct type information object.
|
|
885
|
-
*/
|
|
886
|
-
export const registerStructType = (
|
|
887
|
-
name: string,
|
|
888
|
-
zeroValue: any,
|
|
889
|
-
methods: MethodSignature[],
|
|
890
|
-
ctor: new (...args: any[]) => any,
|
|
891
|
-
fields: Record<string, TypeInfo | string> = {},
|
|
892
|
-
): StructTypeInfo => {
|
|
893
|
-
const typeInfo: StructTypeInfo = {
|
|
894
|
-
name,
|
|
895
|
-
kind: TypeKind.Struct,
|
|
896
|
-
zeroValue,
|
|
897
|
-
methods,
|
|
898
|
-
ctor,
|
|
899
|
-
fields,
|
|
900
|
-
}
|
|
901
|
-
typeRegistry.set(name, typeInfo)
|
|
902
|
-
return typeInfo
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* Registers an interface type with the runtime type system.
|
|
907
|
-
*
|
|
908
|
-
* @param name The name of the type.
|
|
909
|
-
* @param zeroValue The zero value for the type (usually null).
|
|
910
|
-
* @param methods Array of method signatures for the interface.
|
|
911
|
-
* @returns The interface type information object.
|
|
912
|
-
*/
|
|
913
|
-
export const registerInterfaceType = (
|
|
914
|
-
name: string,
|
|
915
|
-
zeroValue: any,
|
|
916
|
-
methods: MethodSignature[],
|
|
917
|
-
): InterfaceTypeInfo => {
|
|
918
|
-
const typeInfo: InterfaceTypeInfo = {
|
|
919
|
-
name,
|
|
920
|
-
kind: TypeKind.Interface,
|
|
921
|
-
zeroValue,
|
|
922
|
-
methods,
|
|
923
|
-
}
|
|
924
|
-
typeRegistry.set(name, typeInfo)
|
|
925
|
-
return typeInfo
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* Represents the result of a type assertion.
|
|
930
|
-
*/
|
|
931
|
-
export interface TypeAssertResult<T> {
|
|
932
|
-
value: T
|
|
933
|
-
ok: boolean
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
/**
|
|
937
|
-
* Normalizes a type info to a structured TypeInfo object.
|
|
938
|
-
*
|
|
939
|
-
* @param info The type info or name.
|
|
940
|
-
* @returns A normalized TypeInfo object.
|
|
941
|
-
*/
|
|
942
|
-
function normalizeTypeInfo(info: string | TypeInfo): TypeInfo {
|
|
943
|
-
if (typeof info === 'string') {
|
|
944
|
-
const typeInfo = typeRegistry.get(info)
|
|
945
|
-
if (typeInfo) {
|
|
946
|
-
return typeInfo
|
|
947
|
-
}
|
|
948
|
-
return {
|
|
949
|
-
kind: TypeKind.Basic,
|
|
950
|
-
name: info,
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
return info
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
function compareOptionalTypeInfo(
|
|
958
|
-
type1?: string | TypeInfo,
|
|
959
|
-
type2?: string | TypeInfo,
|
|
960
|
-
): boolean {
|
|
961
|
-
if (type1 === undefined && type2 === undefined) return true
|
|
962
|
-
if (type1 === undefined || type2 === undefined) return false
|
|
963
|
-
// Assuming areTypeInfosIdentical will handle normalization if needed,
|
|
964
|
-
// but type1 and type2 here are expected to be direct fields from TypeInfo objects.
|
|
965
|
-
return areTypeInfosIdentical(type1, type2)
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
function areFuncParamOrResultArraysIdentical(
|
|
969
|
-
arr1?: (string | TypeInfo)[],
|
|
970
|
-
arr2?: (string | TypeInfo)[]
|
|
971
|
-
): boolean {
|
|
972
|
-
if (arr1 === undefined && arr2 === undefined) return true
|
|
973
|
-
if (arr1 === undefined || arr2 === undefined) return false
|
|
974
|
-
if (arr1.length !== arr2.length) return false
|
|
975
|
-
for (let i = 0; i < arr1.length; i++) {
|
|
976
|
-
if (!areTypeInfosIdentical(arr1[i], arr2[i])) {
|
|
977
|
-
return false
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
return true
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
function areFuncSignaturesIdentical(
|
|
984
|
-
func1: FunctionTypeInfo,
|
|
985
|
-
func2: FunctionTypeInfo,
|
|
986
|
-
): boolean {
|
|
987
|
-
if ((func1.isVariadic || false) !== (func2.isVariadic || false)) {
|
|
988
|
-
return false
|
|
989
|
-
}
|
|
990
|
-
return (
|
|
991
|
-
areFuncParamOrResultArraysIdentical(func1.params, func2.params) &&
|
|
992
|
-
areFuncParamOrResultArraysIdentical(func1.results, func2.results)
|
|
993
|
-
)
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
function areMethodArgsArraysIdentical(
|
|
997
|
-
args1?: MethodArg[],
|
|
998
|
-
args2?: MethodArg[],
|
|
999
|
-
): boolean {
|
|
1000
|
-
if (args1 === undefined && args2 === undefined) return true
|
|
1001
|
-
if (args1 === undefined || args2 === undefined) return false
|
|
1002
|
-
if (args1.length !== args2.length) return false
|
|
1003
|
-
for (let i = 0; i < args1.length; i++) {
|
|
1004
|
-
// Compare based on type only, names of args/results don't affect signature identity here.
|
|
1005
|
-
if (!areTypeInfosIdentical(args1[i].type, args2[i].type)) {
|
|
1006
|
-
return false
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return true
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
export function areTypeInfosIdentical(
|
|
1013
|
-
type1InfoOrName: string | TypeInfo,
|
|
1014
|
-
type2InfoOrName: string | TypeInfo,
|
|
1015
|
-
): boolean {
|
|
1016
|
-
const t1Norm = normalizeTypeInfo(type1InfoOrName)
|
|
1017
|
-
const t2Norm = normalizeTypeInfo(type2InfoOrName)
|
|
1018
|
-
|
|
1019
|
-
if (t1Norm === t2Norm) return true // Object identity
|
|
1020
|
-
if (t1Norm.kind !== t2Norm.kind) return false
|
|
1021
|
-
|
|
1022
|
-
// If types have names, the names must match for identity.
|
|
1023
|
-
// If one has a name and the other doesn't, they are not identical.
|
|
1024
|
-
if (t1Norm.name !== t2Norm.name) return false
|
|
1025
|
-
|
|
1026
|
-
// If both are named and names match, for Basic, Struct, Interface, this is sufficient for identity.
|
|
1027
|
-
if (t1Norm.name !== undefined /* && t2Norm.name is also defined and equal */) {
|
|
1028
|
-
if (
|
|
1029
|
-
t1Norm.kind === TypeKind.Basic ||
|
|
1030
|
-
t1Norm.kind === TypeKind.Struct ||
|
|
1031
|
-
t1Norm.kind === TypeKind.Interface
|
|
1032
|
-
) {
|
|
1033
|
-
return true
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
// For other types (Pointer, Slice, etc.), or if both are anonymous (name is undefined),
|
|
1037
|
-
// structural comparison is needed.
|
|
1038
|
-
|
|
1039
|
-
switch (t1Norm.kind) {
|
|
1040
|
-
case TypeKind.Basic:
|
|
1041
|
-
// Names matched if they were defined, or both undefined (which means true by t1Norm.name !== t2Norm.name being false)
|
|
1042
|
-
return true
|
|
1043
|
-
case TypeKind.Pointer:
|
|
1044
|
-
return compareOptionalTypeInfo(
|
|
1045
|
-
(t1Norm as PointerTypeInfo).elemType,
|
|
1046
|
-
(t2Norm as PointerTypeInfo).elemType,
|
|
1047
|
-
)
|
|
1048
|
-
case TypeKind.Slice:
|
|
1049
|
-
return compareOptionalTypeInfo(
|
|
1050
|
-
(t1Norm as SliceTypeInfo).elemType,
|
|
1051
|
-
(t2Norm as SliceTypeInfo).elemType,
|
|
1052
|
-
)
|
|
1053
|
-
case TypeKind.Array:
|
|
1054
|
-
return (
|
|
1055
|
-
(t1Norm as ArrayTypeInfo).length === (t2Norm as ArrayTypeInfo).length &&
|
|
1056
|
-
compareOptionalTypeInfo(
|
|
1057
|
-
(t1Norm as ArrayTypeInfo).elemType,
|
|
1058
|
-
(t2Norm as ArrayTypeInfo).elemType,
|
|
1059
|
-
)
|
|
1060
|
-
)
|
|
1061
|
-
case TypeKind.Map:
|
|
1062
|
-
return (
|
|
1063
|
-
compareOptionalTypeInfo(
|
|
1064
|
-
(t1Norm as MapTypeInfo).keyType,
|
|
1065
|
-
(t2Norm as MapTypeInfo).keyType,
|
|
1066
|
-
) &&
|
|
1067
|
-
compareOptionalTypeInfo(
|
|
1068
|
-
(t1Norm as MapTypeInfo).elemType,
|
|
1069
|
-
(t2Norm as MapTypeInfo).elemType,
|
|
1070
|
-
)
|
|
1071
|
-
)
|
|
1072
|
-
case TypeKind.Channel:
|
|
1073
|
-
return (
|
|
1074
|
-
// Ensure direction property exists before comparing, or handle undefined if it can be
|
|
1075
|
-
((t1Norm as ChannelTypeInfo).direction || 'both') === ((t2Norm as ChannelTypeInfo).direction || 'both') &&
|
|
1076
|
-
compareOptionalTypeInfo(
|
|
1077
|
-
(t1Norm as ChannelTypeInfo).elemType,
|
|
1078
|
-
(t2Norm as ChannelTypeInfo).elemType,
|
|
1079
|
-
)
|
|
1080
|
-
)
|
|
1081
|
-
case TypeKind.Function:
|
|
1082
|
-
return areFuncSignaturesIdentical(
|
|
1083
|
-
t1Norm as FunctionTypeInfo,
|
|
1084
|
-
t2Norm as FunctionTypeInfo,
|
|
1085
|
-
)
|
|
1086
|
-
case TypeKind.Struct:
|
|
1087
|
-
case TypeKind.Interface:
|
|
1088
|
-
// If we reach here, names were undefined (both anonymous) or names matched but was not Basic/Struct/Interface.
|
|
1089
|
-
// For anonymous Struct/Interface, strict identity means full structural comparison.
|
|
1090
|
-
// For now, we consider anonymous types not identical unless they are the same object (caught above).
|
|
1091
|
-
// If they were named and matched, 'return true' was hit earlier for these kinds.
|
|
1092
|
-
return false
|
|
1093
|
-
default:
|
|
1094
|
-
return false
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
/**
|
|
1099
|
-
* Validates that a map key matches the expected type info.
|
|
1100
|
-
*
|
|
1101
|
-
* @param key The key to validate
|
|
1102
|
-
* @param keyTypeInfo The normalized type info for the key
|
|
1103
|
-
* @returns True if the key matches the type info, false otherwise
|
|
1104
|
-
*/
|
|
1105
|
-
function validateMapKey(key: any, keyTypeInfo: TypeInfo): boolean {
|
|
1106
|
-
if (keyTypeInfo.kind === TypeKind.Basic) {
|
|
1107
|
-
// For string keys
|
|
1108
|
-
if (keyTypeInfo.name === 'string') {
|
|
1109
|
-
return typeof key === 'string'
|
|
1110
|
-
} else if (
|
|
1111
|
-
keyTypeInfo.name === 'int' ||
|
|
1112
|
-
keyTypeInfo.name === 'float64' ||
|
|
1113
|
-
keyTypeInfo.name === 'number'
|
|
1114
|
-
) {
|
|
1115
|
-
if (typeof key === 'string') {
|
|
1116
|
-
return /^-?\d+(\.\d+)?$/.test(key)
|
|
1117
|
-
} else {
|
|
1118
|
-
return typeof key === 'number'
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
return false
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* Checks if a value matches a basic type info.
|
|
1127
|
-
*
|
|
1128
|
-
* @param value The value to check.
|
|
1129
|
-
* @param info The basic type info to match against.
|
|
1130
|
-
* @returns True if the value matches the basic type, false otherwise.
|
|
1131
|
-
*/
|
|
1132
|
-
function matchesBasicType(value: any, info: TypeInfo): boolean {
|
|
1133
|
-
if (info.name === 'string') return typeof value === 'string'
|
|
1134
|
-
if (info.name === 'number' || info.name === 'int' || info.name === 'float64')
|
|
1135
|
-
return typeof value === 'number'
|
|
1136
|
-
if (info.name === 'boolean' || info.name === 'bool')
|
|
1137
|
-
return typeof value === 'boolean'
|
|
1138
|
-
return false
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
/**
|
|
1142
|
-
* Checks if a value matches a struct type info.
|
|
1143
|
-
*
|
|
1144
|
-
* @param value The value to check.
|
|
1145
|
-
* @param info The struct type info to match against.
|
|
1146
|
-
* @returns True if the value matches the struct type, false otherwise.
|
|
1147
|
-
*/
|
|
1148
|
-
function matchesStructType(value: any, info: TypeInfo): boolean {
|
|
1149
|
-
if (!isStructTypeInfo(info)) return false
|
|
1150
|
-
|
|
1151
|
-
// For structs, use instanceof with the constructor
|
|
1152
|
-
if (info.ctor && value instanceof info.ctor) {
|
|
1153
|
-
return true
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
// Check if the value has all methods defined in the struct's TypeInfo
|
|
1157
|
-
// This is a structural check, not a signature check here.
|
|
1158
|
-
// Signature checks are more relevant for interface satisfaction.
|
|
1159
|
-
if (info.methods && typeof value === 'object' && value !== null) {
|
|
1160
|
-
const allMethodsExist = info.methods.every(
|
|
1161
|
-
(methodSig) => typeof (value as any)[methodSig.name] === 'function',
|
|
1162
|
-
)
|
|
1163
|
-
if (!allMethodsExist) {
|
|
1164
|
-
return false
|
|
1165
|
-
}
|
|
1166
|
-
// Further signature checking could be added here if needed for struct-to-struct assignability
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
if (typeof value === 'object' && value !== null && info.fields) {
|
|
1170
|
-
const fieldNames = Object.keys(info.fields || {})
|
|
1171
|
-
const valueFields = Object.keys(value)
|
|
1172
|
-
|
|
1173
|
-
const fieldsExist = fieldNames.every((field) => field in value)
|
|
1174
|
-
const sameFieldCount = valueFields.length === fieldNames.length
|
|
1175
|
-
const allFieldsInStruct = valueFields.every((field) =>
|
|
1176
|
-
fieldNames.includes(field),
|
|
1177
|
-
)
|
|
1178
|
-
|
|
1179
|
-
if (fieldsExist && sameFieldCount && allFieldsInStruct) {
|
|
1180
|
-
return Object.entries(info.fields).every(([fieldName, fieldType]) => {
|
|
1181
|
-
return matchesType(
|
|
1182
|
-
value[fieldName],
|
|
1183
|
-
normalizeTypeInfo(fieldType as TypeInfo | string),
|
|
1184
|
-
)
|
|
1185
|
-
})
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
return false
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
return false
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* Checks if a value matches an interface type info.
|
|
1196
|
-
*
|
|
1197
|
-
* @param value The value to check.
|
|
1198
|
-
* @param info The interface type info to match against.
|
|
1199
|
-
* @returns True if the value matches the interface type, false otherwise.
|
|
1200
|
-
*/
|
|
1201
|
-
/**
|
|
1202
|
-
* Checks if a value matches an interface type info by verifying it implements
|
|
1203
|
-
* all required methods with compatible signatures.
|
|
1204
|
-
*
|
|
1205
|
-
* @param value The value to check.
|
|
1206
|
-
* @param info The interface type info to match against.
|
|
1207
|
-
* @returns True if the value matches the interface type, false otherwise.
|
|
1208
|
-
*/
|
|
1209
|
-
function matchesInterfaceType(value: any, info: TypeInfo): boolean {
|
|
1210
|
-
// Check basic conditions first
|
|
1211
|
-
if (!isInterfaceTypeInfo(info) || typeof value !== 'object' || value === null) {
|
|
1212
|
-
return false
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
// For interfaces, check if the value has all the required methods with compatible signatures
|
|
1216
|
-
return info.methods.every((requiredMethodSig) => {
|
|
1217
|
-
const actualMethod = (value as any)[requiredMethodSig.name]
|
|
1218
|
-
|
|
1219
|
-
// Method must exist and be a function
|
|
1220
|
-
if (typeof actualMethod !== 'function') {
|
|
1221
|
-
return false
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// Check parameter count (basic arity check)
|
|
1225
|
-
// Note: This is a simplified check as JavaScript functions can have optional/rest parameters
|
|
1226
|
-
const declaredParamCount = actualMethod.length
|
|
1227
|
-
const requiredParamCount = requiredMethodSig.args.length
|
|
1228
|
-
|
|
1229
|
-
// Strict arity checking can be problematic in JS, so we'll be lenient
|
|
1230
|
-
// A method with fewer params than required is definitely incompatible
|
|
1231
|
-
if (declaredParamCount < requiredParamCount) {
|
|
1232
|
-
return false
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
// Check return types if we can determine them
|
|
1236
|
-
// This is challenging in JavaScript without runtime type information
|
|
1237
|
-
|
|
1238
|
-
// If the value has a __goTypeName property, it might be a registered type
|
|
1239
|
-
// with more type information available
|
|
1240
|
-
if (value.__goTypeName) {
|
|
1241
|
-
const valueTypeInfo = typeRegistry.get(value.__goTypeName)
|
|
1242
|
-
if (valueTypeInfo && isStructTypeInfo(valueTypeInfo)) {
|
|
1243
|
-
// Find the matching method in the value's type info
|
|
1244
|
-
const valueMethodSig = valueTypeInfo.methods.find(
|
|
1245
|
-
m => m.name === requiredMethodSig.name
|
|
1246
|
-
)
|
|
1247
|
-
|
|
1248
|
-
if (valueMethodSig) {
|
|
1249
|
-
// Compare return types
|
|
1250
|
-
if (valueMethodSig.returns.length !== requiredMethodSig.returns.length) {
|
|
1251
|
-
return false
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// Compare each return type for compatibility
|
|
1255
|
-
for (let i = 0; i < requiredMethodSig.returns.length; i++) {
|
|
1256
|
-
const requiredReturnType = normalizeTypeInfo(
|
|
1257
|
-
requiredMethodSig.returns[i].type
|
|
1258
|
-
)
|
|
1259
|
-
const valueReturnType = normalizeTypeInfo(
|
|
1260
|
-
valueMethodSig.returns[i].type
|
|
1261
|
-
)
|
|
1262
|
-
|
|
1263
|
-
// For interface return types, we need to check if the value's return type
|
|
1264
|
-
// implements the required interface
|
|
1265
|
-
if (isInterfaceTypeInfo(requiredReturnType)) {
|
|
1266
|
-
// This would be a recursive check, but we'll simplify for now
|
|
1267
|
-
// by just checking if the types are the same or if the value type
|
|
1268
|
-
// is registered as implementing the interface
|
|
1269
|
-
if (requiredReturnType.name !== valueReturnType.name) {
|
|
1270
|
-
// Check if valueReturnType implements requiredReturnType
|
|
1271
|
-
// This would require additional implementation tracking
|
|
1272
|
-
return false
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
// For non-interface types, check direct type compatibility
|
|
1276
|
-
else if (requiredReturnType.name !== valueReturnType.name) {
|
|
1277
|
-
return false
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
// Similarly, we could check parameter types for compatibility
|
|
1282
|
-
// but we'll skip that for brevity
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
// If we can't determine detailed type information, we'll accept the method
|
|
1288
|
-
// as long as it exists with a compatible arity
|
|
1289
|
-
return true
|
|
1290
|
-
})
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
/**
|
|
1294
|
-
* Checks if a value matches a map type info.
|
|
1295
|
-
*
|
|
1296
|
-
* @param value The value to check.
|
|
1297
|
-
* @param info The map type info to match against.
|
|
1298
|
-
* @returns True if the value matches the map type, false otherwise.
|
|
1299
|
-
*/
|
|
1300
|
-
function matchesMapType(value: any, info: TypeInfo): boolean {
|
|
1301
|
-
if (typeof value !== 'object' || value === null) return false
|
|
1302
|
-
if (!isMapTypeInfo(info)) return false
|
|
1303
|
-
|
|
1304
|
-
if (info.keyType || info.elemType) {
|
|
1305
|
-
let entries: [any, any][] = []
|
|
1306
|
-
|
|
1307
|
-
if (value instanceof Map) {
|
|
1308
|
-
entries = Array.from(value.entries())
|
|
1309
|
-
} else {
|
|
1310
|
-
entries = Object.entries(value)
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
if (entries.length === 0) return true // Empty map matches any map type
|
|
1314
|
-
|
|
1315
|
-
const sampleSize = Math.min(5, entries.length)
|
|
1316
|
-
for (let i = 0; i < sampleSize; i++) {
|
|
1317
|
-
const [k, v] = entries[i]
|
|
1318
|
-
|
|
1319
|
-
if (info.keyType) {
|
|
1320
|
-
if (
|
|
1321
|
-
!validateMapKey(
|
|
1322
|
-
k,
|
|
1323
|
-
normalizeTypeInfo(info.keyType as string | TypeInfo),
|
|
1324
|
-
)
|
|
1325
|
-
) {
|
|
1326
|
-
return false
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
if (
|
|
1331
|
-
info.elemType &&
|
|
1332
|
-
!matchesType(v, normalizeTypeInfo(info.elemType as string | TypeInfo))
|
|
1333
|
-
) {
|
|
1334
|
-
return false
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
return true
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
/**
|
|
1343
|
-
* Checks if a value matches an array or slice type info.
|
|
1344
|
-
*
|
|
1345
|
-
* @param value The value to check.
|
|
1346
|
-
* @param info The array or slice type info to match against.
|
|
1347
|
-
* @returns True if the value matches the array or slice type, false otherwise.
|
|
1348
|
-
*/
|
|
1349
|
-
function matchesArrayOrSliceType(value: any, info: TypeInfo): boolean {
|
|
1350
|
-
// For slices and arrays, check if the value is an array and sample element types
|
|
1351
|
-
if (!Array.isArray(value)) return false
|
|
1352
|
-
if (!isArrayTypeInfo(info) && !isSliceTypeInfo(info)) return false
|
|
1353
|
-
|
|
1354
|
-
if (info.elemType) {
|
|
1355
|
-
const arr = value as any[]
|
|
1356
|
-
if (arr.length === 0) return true // Empty array matches any array type
|
|
1357
|
-
|
|
1358
|
-
const sampleSize = Math.min(5, arr.length)
|
|
1359
|
-
for (let i = 0; i < sampleSize; i++) {
|
|
1360
|
-
if (
|
|
1361
|
-
!matchesType(
|
|
1362
|
-
arr[i],
|
|
1363
|
-
normalizeTypeInfo(info.elemType as string | TypeInfo),
|
|
1364
|
-
)
|
|
1365
|
-
) {
|
|
1366
|
-
return false
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
return true
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
/**
|
|
1375
|
-
* Checks if a value matches a pointer type info.
|
|
1376
|
-
*
|
|
1377
|
-
* @param value The value to check.
|
|
1378
|
-
* @param info The pointer type info to match against.
|
|
1379
|
-
* @returns True if the value matches the pointer type, false otherwise.
|
|
1380
|
-
*/
|
|
1381
|
-
function matchesPointerType(value: any, info: TypeInfo): boolean {
|
|
1382
|
-
// Allow null/undefined values to match pointer types to support nil pointer assertions
|
|
1383
|
-
// This enables Go's nil pointer type assertions like `ptr, ok := i.(*SomeType)` to work correctly
|
|
1384
|
-
if (value === null || value === undefined) {
|
|
1385
|
-
return true
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// Check if the value is a Box (has a 'value' property)
|
|
1389
|
-
if (typeof value !== 'object' || !('value' in value)) {
|
|
1390
|
-
return false
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
if (!isPointerTypeInfo(info)) return false
|
|
1394
|
-
|
|
1395
|
-
if (info.elemType) {
|
|
1396
|
-
const elemTypeInfo = normalizeTypeInfo(info.elemType as string | TypeInfo)
|
|
1397
|
-
return matchesType(value.value, elemTypeInfo)
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
return true
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
/**
|
|
1404
|
-
* Checks if a value matches a function type info.
|
|
1405
|
-
*
|
|
1406
|
-
* @param value The value to check.
|
|
1407
|
-
* @param info The function type info to match against.
|
|
1408
|
-
* @returns True if the value matches the function type, false otherwise.
|
|
1409
|
-
*/
|
|
1410
|
-
function matchesFunctionType(value: any, info: FunctionTypeInfo): boolean {
|
|
1411
|
-
// First check if the value is a function
|
|
1412
|
-
if (typeof value !== 'function') {
|
|
1413
|
-
return false
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
// This is important for named function types
|
|
1417
|
-
if (info.name && value.__goTypeName) {
|
|
1418
|
-
return info.name === value.__goTypeName
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
return true
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
* Checks if a value matches a channel type info.
|
|
1426
|
-
*
|
|
1427
|
-
* @param value The value to check.
|
|
1428
|
-
* @param info The channel type info to match against.
|
|
1429
|
-
* @returns True if the value matches the channel type, false otherwise.
|
|
1430
|
-
*/
|
|
1431
|
-
function matchesChannelType(value: any, info: ChannelTypeInfo): boolean {
|
|
1432
|
-
// First check if it's a channel or channel reference
|
|
1433
|
-
if (typeof value !== 'object' || value === null) {
|
|
1434
|
-
return false
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
// If it's a ChannelRef, get the underlying channel
|
|
1438
|
-
let channel = value
|
|
1439
|
-
let valueDirection = 'both'
|
|
1440
|
-
|
|
1441
|
-
if ('channel' in value && 'direction' in value) {
|
|
1442
|
-
channel = value.channel
|
|
1443
|
-
valueDirection = value.direction
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// Check if it has channel methods
|
|
1447
|
-
if (
|
|
1448
|
-
!('send' in channel) ||
|
|
1449
|
-
!('receive' in channel) ||
|
|
1450
|
-
!('close' in channel) ||
|
|
1451
|
-
typeof channel.send !== 'function' ||
|
|
1452
|
-
typeof channel.receive !== 'function' ||
|
|
1453
|
-
typeof channel.close !== 'function'
|
|
1454
|
-
) {
|
|
1455
|
-
return false
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
if (info.elemType) {
|
|
1459
|
-
if (
|
|
1460
|
-
info.elemType === 'string' &&
|
|
1461
|
-
'zeroValue' in channel &&
|
|
1462
|
-
channel.zeroValue !== ''
|
|
1463
|
-
) {
|
|
1464
|
-
return false
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
if (
|
|
1468
|
-
info.elemType === 'number' &&
|
|
1469
|
-
'zeroValue' in channel &&
|
|
1470
|
-
typeof channel.zeroValue !== 'number'
|
|
1471
|
-
) {
|
|
1472
|
-
return false
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
if (info.direction) {
|
|
1477
|
-
return valueDirection === info.direction
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
return true
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
/**
|
|
1484
|
-
* Checks if a value matches a type info.
|
|
1485
|
-
*
|
|
1486
|
-
* @param value The value to check.
|
|
1487
|
-
* @param info The type info to match against.
|
|
1488
|
-
* @returns True if the value matches the type info, false otherwise.
|
|
1489
|
-
*/
|
|
1490
|
-
function matchesType(value: any, info: TypeInfo): boolean {
|
|
1491
|
-
if (value === null || value === undefined) {
|
|
1492
|
-
return false
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
switch (info.kind) {
|
|
1496
|
-
case TypeKind.Basic:
|
|
1497
|
-
return matchesBasicType(value, info)
|
|
1498
|
-
|
|
1499
|
-
case TypeKind.Struct:
|
|
1500
|
-
return matchesStructType(value, info)
|
|
1501
|
-
|
|
1502
|
-
case TypeKind.Interface:
|
|
1503
|
-
return matchesInterfaceType(value, info)
|
|
1504
|
-
|
|
1505
|
-
case TypeKind.Map:
|
|
1506
|
-
return matchesMapType(value, info)
|
|
1507
|
-
|
|
1508
|
-
case TypeKind.Slice:
|
|
1509
|
-
case TypeKind.Array:
|
|
1510
|
-
return matchesArrayOrSliceType(value, info)
|
|
1511
|
-
|
|
1512
|
-
case TypeKind.Pointer:
|
|
1513
|
-
return matchesPointerType(value, info)
|
|
1514
|
-
|
|
1515
|
-
case TypeKind.Function:
|
|
1516
|
-
return matchesFunctionType(value, info as FunctionTypeInfo)
|
|
1517
|
-
|
|
1518
|
-
case TypeKind.Channel:
|
|
1519
|
-
return matchesChannelType(value, info)
|
|
1520
|
-
|
|
1521
|
-
default:
|
|
1522
|
-
console.warn(
|
|
1523
|
-
`Type matching for kind '${(info as TypeInfo).kind}' not implemented.`,
|
|
1524
|
-
)
|
|
1525
|
-
return false
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
export function typeAssert<T>(
|
|
1530
|
-
value: any,
|
|
1531
|
-
typeInfo: string | TypeInfo,
|
|
1532
|
-
): TypeAssertResult<T> {
|
|
1533
|
-
const normalizedType = normalizeTypeInfo(typeInfo)
|
|
1534
|
-
|
|
1535
|
-
if (isPointerTypeInfo(normalizedType) && value === null) {
|
|
1536
|
-
return { value: null as unknown as T, ok: true }
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
if (
|
|
1540
|
-
isStructTypeInfo(normalizedType) &&
|
|
1541
|
-
normalizedType.methods && normalizedType.methods.length > 0 &&
|
|
1542
|
-
typeof value === 'object' &&
|
|
1543
|
-
value !== null
|
|
1544
|
-
) {
|
|
1545
|
-
// Check if the value implements all methods of the struct type with compatible signatures.
|
|
1546
|
-
// This is more for interface satisfaction by a struct.
|
|
1547
|
-
// For struct-to-struct assertion, usually instanceof or field checks are primary.
|
|
1548
|
-
const allMethodsMatch = normalizedType.methods.every((requiredMethodSig) => {
|
|
1549
|
-
const actualMethod = (value as any)[requiredMethodSig.name];
|
|
1550
|
-
if (typeof actualMethod !== 'function') {
|
|
1551
|
-
return false;
|
|
1552
|
-
}
|
|
1553
|
-
const valueTypeInfoVal = (value as any).$typeInfo
|
|
1554
|
-
if (valueTypeInfoVal) {
|
|
1555
|
-
const normalizedValueType = normalizeTypeInfo(valueTypeInfoVal)
|
|
1556
|
-
if (isStructTypeInfo(normalizedValueType) || isInterfaceTypeInfo(normalizedValueType)) {
|
|
1557
|
-
const actualValueMethodSig = normalizedValueType.methods.find(m => m.name === requiredMethodSig.name)
|
|
1558
|
-
if (actualValueMethodSig) {
|
|
1559
|
-
// Perform full signature comparison using MethodSignatures
|
|
1560
|
-
const paramsMatch = areMethodArgsArraysIdentical(requiredMethodSig.args, actualValueMethodSig.args)
|
|
1561
|
-
const resultsMatch = areMethodArgsArraysIdentical(requiredMethodSig.returns, actualValueMethodSig.returns)
|
|
1562
|
-
return paramsMatch && resultsMatch
|
|
1563
|
-
} else {
|
|
1564
|
-
// Value has TypeInfo listing methods, but this specific method isn't listed.
|
|
1565
|
-
// This implies a mismatch for strict signature check based on TypeInfo.
|
|
1566
|
-
return false
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
// Fallback: Original behavior if value has no TypeInfo that lists methods,
|
|
1572
|
-
// or if the method wasn't found in its TypeInfo (covered by 'else' returning false above).
|
|
1573
|
-
// The original comment was: "For now, presence and function type is checked by matchesStructType/matchesInterfaceType"
|
|
1574
|
-
// This 'return true' implies that if we couldn't do a full signature check via TypeInfo,
|
|
1575
|
-
// we still consider it a match if the function simply exists on the object.
|
|
1576
|
-
return true;
|
|
1577
|
-
});
|
|
1578
|
-
|
|
1579
|
-
if (allMethodsMatch) {
|
|
1580
|
-
return { value: value as T, ok: true };
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
if (
|
|
1585
|
-
isStructTypeInfo(normalizedType) &&
|
|
1586
|
-
normalizedType.fields &&
|
|
1587
|
-
typeof value === 'object' &&
|
|
1588
|
-
value !== null
|
|
1589
|
-
) {
|
|
1590
|
-
const fieldNames = Object.keys(normalizedType.fields)
|
|
1591
|
-
const valueFields = Object.keys(value)
|
|
1592
|
-
|
|
1593
|
-
// For struct type assertions, we need exact field matching
|
|
1594
|
-
const structFieldsMatch =
|
|
1595
|
-
fieldNames.length === valueFields.length &&
|
|
1596
|
-
fieldNames.every((field: string) => field in value) &&
|
|
1597
|
-
valueFields.every((field) => fieldNames.includes(field))
|
|
1598
|
-
|
|
1599
|
-
if (structFieldsMatch) {
|
|
1600
|
-
const typesMatch = Object.entries(normalizedType.fields).every(
|
|
1601
|
-
([fieldName, fieldType]) => {
|
|
1602
|
-
return matchesType(
|
|
1603
|
-
value[fieldName],
|
|
1604
|
-
normalizeTypeInfo(fieldType as TypeInfo | string),
|
|
1605
|
-
)
|
|
1606
|
-
},
|
|
1607
|
-
)
|
|
1608
|
-
|
|
1609
|
-
return { value: value as T, ok: typesMatch }
|
|
1610
|
-
} else {
|
|
1611
|
-
return { value: null as unknown as T, ok: false }
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
if (
|
|
1616
|
-
isMapTypeInfo(normalizedType) &&
|
|
1617
|
-
typeof value === 'object' &&
|
|
1618
|
-
value !== null
|
|
1619
|
-
) {
|
|
1620
|
-
if (normalizedType.keyType || normalizedType.elemType) {
|
|
1621
|
-
let entries: [any, any][] = []
|
|
1622
|
-
|
|
1623
|
-
if (value instanceof Map) {
|
|
1624
|
-
entries = Array.from(value.entries())
|
|
1625
|
-
} else {
|
|
1626
|
-
entries = Object.entries(value)
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
if (entries.length === 0) {
|
|
1630
|
-
return { value: value as T, ok: true }
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
const sampleSize = Math.min(5, entries.length)
|
|
1634
|
-
for (let i = 0; i < sampleSize; i++) {
|
|
1635
|
-
const [k, v] = entries[i]
|
|
1636
|
-
|
|
1637
|
-
if (normalizedType.keyType) {
|
|
1638
|
-
if (
|
|
1639
|
-
!validateMapKey(
|
|
1640
|
-
k,
|
|
1641
|
-
normalizeTypeInfo(normalizedType.keyType as string | TypeInfo),
|
|
1642
|
-
)
|
|
1643
|
-
) {
|
|
1644
|
-
return { value: null as unknown as T, ok: false }
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
if (normalizedType.elemType) {
|
|
1649
|
-
const elemTypeInfo = normalizeTypeInfo(
|
|
1650
|
-
normalizedType.elemType as string | TypeInfo,
|
|
1651
|
-
)
|
|
1652
|
-
if (!matchesType(v, elemTypeInfo)) {
|
|
1653
|
-
return { value: null as unknown as T, ok: false }
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
// If we get here, the map type assertion passes
|
|
1659
|
-
return { value: value as T, ok: true }
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
const matches = matchesType(value, normalizedType)
|
|
1664
|
-
|
|
1665
|
-
if (matches) {
|
|
1666
|
-
return { value: value as T, ok: true }
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
// If we get here, the assertion failed
|
|
1670
|
-
// For registered types, use the zero value from the registry
|
|
1671
|
-
if (typeof typeInfo === 'string') {
|
|
1672
|
-
const registeredType = typeRegistry.get(typeInfo)
|
|
1673
|
-
if (registeredType && registeredType.zeroValue !== undefined) {
|
|
1674
|
-
return { value: registeredType.zeroValue as T, ok: false }
|
|
1675
|
-
}
|
|
1676
|
-
} else if (normalizedType.zeroValue !== undefined) {
|
|
1677
|
-
return { value: normalizedType.zeroValue as T, ok: false }
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
return { value: null as unknown as T, ok: false }
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
/**
|
|
1684
|
-
* Represents the result of a channel receive operation with 'ok' value
|
|
1685
|
-
*/
|
|
1686
|
-
export interface ChannelReceiveResult<T> {
|
|
1687
|
-
value: T // Should be T | ZeroValue<T>
|
|
1688
|
-
ok: boolean
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
/**
|
|
1692
|
-
* Represents a result from a select operation
|
|
1693
|
-
*/
|
|
1694
|
-
export interface SelectResult<T> {
|
|
1695
|
-
value: T // Should be T | ZeroValue<T>
|
|
1696
|
-
ok: boolean
|
|
1697
|
-
id: number
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
/**
|
|
1701
|
-
* Represents a Go channel in TypeScript.
|
|
1702
|
-
* Supports asynchronous sending and receiving of values.
|
|
1703
|
-
*/
|
|
1704
|
-
export interface Channel<T> {
|
|
1705
|
-
/**
|
|
1706
|
-
* Sends a value to the channel.
|
|
1707
|
-
* Returns a promise that resolves when the value is accepted by the channel.
|
|
1708
|
-
* @param value The value to send.
|
|
1709
|
-
*/
|
|
1710
|
-
send(value: T): Promise<void>
|
|
1711
|
-
|
|
1712
|
-
/**
|
|
1713
|
-
* Receives a value from the channel.
|
|
1714
|
-
* Returns a promise that resolves with the received value.
|
|
1715
|
-
* If the channel is closed, it throws an error.
|
|
1716
|
-
*/
|
|
1717
|
-
receive(): Promise<T>
|
|
1718
|
-
|
|
1719
|
-
/**
|
|
1720
|
-
* Receives a value from the channel along with a boolean indicating
|
|
1721
|
-
* whether the channel is still open.
|
|
1722
|
-
* Returns a promise that resolves with {value, ok}.
|
|
1723
|
-
* - If channel is open and has data: {value: <data>, ok: true}
|
|
1724
|
-
* - If channel is closed and empty: {value: <zero value>, ok: false}
|
|
1725
|
-
* - If channel is closed but has remaining buffered data: {value: <data>, ok: true}
|
|
1726
|
-
*/
|
|
1727
|
-
receiveWithOk(): Promise<ChannelReceiveResult<T>>
|
|
1728
|
-
|
|
1729
|
-
/**
|
|
1730
|
-
* Closes the channel.
|
|
1731
|
-
* No more values can be sent to a closed channel.
|
|
1732
|
-
* Receive operations on a closed channel return the zero value and ok=false.
|
|
1733
|
-
*/
|
|
1734
|
-
close(): void
|
|
1735
|
-
|
|
1736
|
-
/**
|
|
1737
|
-
* Used in select statements to create a receive operation promise.
|
|
1738
|
-
* @param id An identifier for this case in the select statement
|
|
1739
|
-
* @returns Promise that resolves when this case is selected
|
|
1740
|
-
*/
|
|
1741
|
-
selectReceive(id: number): Promise<SelectResult<T>>
|
|
1742
|
-
|
|
1743
|
-
/**
|
|
1744
|
-
* Used in select statements to create a send operation promise.
|
|
1745
|
-
* @param value The value to send
|
|
1746
|
-
* @param id An identifier for this case in the select statement
|
|
1747
|
-
* @returns Promise that resolves when this case is selected
|
|
1748
|
-
*/
|
|
1749
|
-
selectSend(value: T, id: number): Promise<SelectResult<boolean>>
|
|
1750
|
-
|
|
1751
|
-
/**
|
|
1752
|
-
* Checks if the channel has data ready to be received without blocking.
|
|
1753
|
-
* Used for non-blocking select operations.
|
|
1754
|
-
*/
|
|
1755
|
-
canReceiveNonBlocking(): boolean
|
|
1756
|
-
|
|
1757
|
-
/**
|
|
1758
|
-
* Checks if the channel can accept a send operation without blocking.
|
|
1759
|
-
* Used for non-blocking select operations.
|
|
1760
|
-
*/
|
|
1761
|
-
canSendNonBlocking(): boolean
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
// A simple implementation of buffered channels
|
|
1765
|
-
class BufferedChannel<T> implements Channel<T> {
|
|
1766
|
-
private buffer: T[] = []
|
|
1767
|
-
private closed: boolean = false
|
|
1768
|
-
private capacity: number
|
|
1769
|
-
public zeroValue: T // Made public for access by ChannelRef or for type inference
|
|
1770
|
-
|
|
1771
|
-
// Senders queue: stores { value, resolve for send, reject for send }
|
|
1772
|
-
private senders: Array<{
|
|
1773
|
-
value: T
|
|
1774
|
-
resolveSend: () => void
|
|
1775
|
-
rejectSend: (e: Error) => void
|
|
1776
|
-
}> = []
|
|
1777
|
-
|
|
1778
|
-
// Receivers queue for receive(): stores { resolve for receive, reject for receive }
|
|
1779
|
-
private receivers: Array<{
|
|
1780
|
-
resolveReceive: (value: T) => void
|
|
1781
|
-
rejectReceive: (e: Error) => void
|
|
1782
|
-
}> = []
|
|
1783
|
-
|
|
1784
|
-
// Receivers queue for receiveWithOk(): stores { resolve for receiveWithOk }
|
|
1785
|
-
private receiversWithOk: Array<{
|
|
1786
|
-
resolveReceive: (result: ChannelReceiveResult<T>) => void
|
|
1787
|
-
}> = []
|
|
1788
|
-
|
|
1789
|
-
constructor(capacity: number, zeroValue: T) {
|
|
1790
|
-
if (capacity < 0) {
|
|
1791
|
-
throw new Error('Channel capacity cannot be negative')
|
|
1792
|
-
}
|
|
1793
|
-
this.capacity = capacity
|
|
1794
|
-
this.zeroValue = zeroValue
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
async send(value: T): Promise<void> {
|
|
1798
|
-
if (this.closed) {
|
|
1799
|
-
throw new Error('send on closed channel')
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
// Attempt to hand off to a waiting receiver (rendezvous)
|
|
1803
|
-
if (this.receivers.length > 0) {
|
|
1804
|
-
const receiverTask = this.receivers.shift()!
|
|
1805
|
-
queueMicrotask(() => receiverTask.resolveReceive(value))
|
|
1806
|
-
return
|
|
1807
|
-
}
|
|
1808
|
-
if (this.receiversWithOk.length > 0) {
|
|
1809
|
-
const receiverTask = this.receiversWithOk.shift()!
|
|
1810
|
-
queueMicrotask(() => receiverTask.resolveReceive({ value, ok: true }))
|
|
1811
|
-
return
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
// If no waiting receivers, try to buffer if space is available
|
|
1815
|
-
if (this.buffer.length < this.capacity) {
|
|
1816
|
-
this.buffer.push(value)
|
|
1817
|
-
return
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
// Buffer is full (or capacity is 0 and no receivers are waiting). Sender must block.
|
|
1821
|
-
return new Promise<void>((resolve, reject) => {
|
|
1822
|
-
this.senders.push({ value, resolveSend: resolve, rejectSend: reject })
|
|
1823
|
-
})
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
async receive(): Promise<T> {
|
|
1827
|
-
// Attempt to get from buffer first
|
|
1828
|
-
if (this.buffer.length > 0) {
|
|
1829
|
-
const value = this.buffer.shift()!
|
|
1830
|
-
// If a sender was waiting because the buffer was full, unblock it.
|
|
1831
|
-
if (this.senders.length > 0) {
|
|
1832
|
-
const senderTask = this.senders.shift()!
|
|
1833
|
-
this.buffer.push(senderTask.value) // Sender's value now goes into buffer
|
|
1834
|
-
queueMicrotask(() => senderTask.resolveSend()) // Unblock sender
|
|
1835
|
-
}
|
|
1836
|
-
return value
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
// Buffer is empty.
|
|
1840
|
-
// If channel is closed (and buffer is empty), subsequent receives panic.
|
|
1841
|
-
if (this.closed) {
|
|
1842
|
-
throw new Error('receive on closed channel')
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
// Buffer is empty, channel is open.
|
|
1846
|
-
// Attempt to rendezvous with a waiting sender.
|
|
1847
|
-
if (this.senders.length > 0) {
|
|
1848
|
-
const senderTask = this.senders.shift()!
|
|
1849
|
-
queueMicrotask(() => senderTask.resolveSend()) // Unblock the sender
|
|
1850
|
-
return senderTask.value // Return the value from sender
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
// Buffer is empty, channel is open, no waiting senders. Receiver must block.
|
|
1854
|
-
return new Promise<T>((resolve, reject) => {
|
|
1855
|
-
this.receivers.push({ resolveReceive: resolve, rejectReceive: reject })
|
|
1856
|
-
})
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
async receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
1860
|
-
// Attempt to get from buffer first
|
|
1861
|
-
if (this.buffer.length > 0) {
|
|
1862
|
-
const value = this.buffer.shift()!
|
|
1863
|
-
if (this.senders.length > 0) {
|
|
1864
|
-
const senderTask = this.senders.shift()!
|
|
1865
|
-
this.buffer.push(senderTask.value)
|
|
1866
|
-
queueMicrotask(() => senderTask.resolveSend())
|
|
1867
|
-
}
|
|
1868
|
-
return { value, ok: true }
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
// Buffer is empty.
|
|
1872
|
-
// Attempt to rendezvous with a waiting sender.
|
|
1873
|
-
if (this.senders.length > 0) {
|
|
1874
|
-
const senderTask = this.senders.shift()!
|
|
1875
|
-
queueMicrotask(() => senderTask.resolveSend())
|
|
1876
|
-
return { value: senderTask.value, ok: true }
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
// Buffer is empty, no waiting senders.
|
|
1880
|
-
// If channel is closed, return zero value with ok: false.
|
|
1881
|
-
if (this.closed) {
|
|
1882
|
-
return { value: this.zeroValue, ok: false }
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
// Buffer is empty, channel is open, no waiting senders. Receiver must block.
|
|
1886
|
-
return new Promise<ChannelReceiveResult<T>>((resolve) => {
|
|
1887
|
-
this.receiversWithOk.push({ resolveReceive: resolve })
|
|
1888
|
-
})
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
async selectReceive(id: number): Promise<SelectResult<T>> {
|
|
1892
|
-
if (this.buffer.length > 0) {
|
|
1893
|
-
const value = this.buffer.shift()!
|
|
1894
|
-
if (this.senders.length > 0) {
|
|
1895
|
-
const senderTask = this.senders.shift()!
|
|
1896
|
-
this.buffer.push(senderTask.value)
|
|
1897
|
-
queueMicrotask(() => senderTask.resolveSend())
|
|
1898
|
-
}
|
|
1899
|
-
return { value, ok: true, id }
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
if (this.senders.length > 0) {
|
|
1903
|
-
const senderTask = this.senders.shift()!
|
|
1904
|
-
queueMicrotask(() => senderTask.resolveSend())
|
|
1905
|
-
return { value: senderTask.value, ok: true, id }
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
if (this.closed) {
|
|
1909
|
-
return { value: this.zeroValue, ok: false, id }
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
return new Promise<SelectResult<T>>((resolve) => {
|
|
1913
|
-
this.receiversWithOk.push({
|
|
1914
|
-
resolveReceive: (result: ChannelReceiveResult<T>) => {
|
|
1915
|
-
resolve({ ...result, id })
|
|
1916
|
-
},
|
|
1917
|
-
})
|
|
1918
|
-
})
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
async selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
1922
|
-
if (this.closed) {
|
|
1923
|
-
// A select case sending on a closed channel panics in Go.
|
|
1924
|
-
// This will cause Promise.race in selectStatement to reject.
|
|
1925
|
-
throw new Error('send on closed channel')
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
if (this.receivers.length > 0) {
|
|
1929
|
-
const receiverTask = this.receivers.shift()!
|
|
1930
|
-
queueMicrotask(() => receiverTask.resolveReceive(value))
|
|
1931
|
-
return { value: true, ok: true, id }
|
|
1932
|
-
}
|
|
1933
|
-
if (this.receiversWithOk.length > 0) {
|
|
1934
|
-
const receiverTask = this.receiversWithOk.shift()!
|
|
1935
|
-
queueMicrotask(() => receiverTask.resolveReceive({ value, ok: true }))
|
|
1936
|
-
return { value: true, ok: true, id }
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
if (this.buffer.length < this.capacity) {
|
|
1940
|
-
this.buffer.push(value)
|
|
1941
|
-
return { value: true, ok: true, id }
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
return new Promise<SelectResult<boolean>>((resolve, reject) => {
|
|
1945
|
-
this.senders.push({
|
|
1946
|
-
value,
|
|
1947
|
-
resolveSend: () => resolve({ value: true, ok: true, id }),
|
|
1948
|
-
rejectSend: (e) => reject(e), // Propagate error if channel closes
|
|
1949
|
-
})
|
|
1950
|
-
})
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
close(): void {
|
|
1954
|
-
if (this.closed) {
|
|
1955
|
-
throw new Error('close of closed channel')
|
|
1956
|
-
}
|
|
1957
|
-
this.closed = true
|
|
1958
|
-
|
|
1959
|
-
const sendersToNotify = [...this.senders] // Shallow copy for iteration
|
|
1960
|
-
this.senders = []
|
|
1961
|
-
for (const senderTask of sendersToNotify) {
|
|
1962
|
-
queueMicrotask(() =>
|
|
1963
|
-
senderTask.rejectSend(new Error('send on closed channel')),
|
|
1964
|
-
)
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
const receiversToNotify = [...this.receivers]
|
|
1968
|
-
this.receivers = []
|
|
1969
|
-
for (const receiverTask of receiversToNotify) {
|
|
1970
|
-
queueMicrotask(() =>
|
|
1971
|
-
receiverTask.rejectReceive(new Error('receive on closed channel')),
|
|
1972
|
-
)
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
const receiversWithOkToNotify = [...this.receiversWithOk]
|
|
1976
|
-
this.receiversWithOk = []
|
|
1977
|
-
for (const receiverTask of receiversWithOkToNotify) {
|
|
1978
|
-
queueMicrotask(() =>
|
|
1979
|
-
receiverTask.resolveReceive({ value: this.zeroValue, ok: false }),
|
|
1980
|
-
)
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
canReceiveNonBlocking(): boolean {
|
|
1985
|
-
return this.buffer.length > 0 || this.senders.length > 0 || this.closed
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
canSendNonBlocking(): boolean {
|
|
1989
|
-
if (this.closed) {
|
|
1990
|
-
return true // Ready to panic
|
|
1991
|
-
}
|
|
1992
|
-
return (
|
|
1993
|
-
this.buffer.length < this.capacity ||
|
|
1994
|
-
this.receivers.length > 0 ||
|
|
1995
|
-
this.receiversWithOk.length > 0
|
|
1996
|
-
)
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
/**
|
|
2001
|
-
* Represents a reference to a channel with a specific direction.
|
|
2002
|
-
*/
|
|
2003
|
-
export interface ChannelRef<T> {
|
|
2004
|
-
/**
|
|
2005
|
-
* The underlying channel
|
|
2006
|
-
*/
|
|
2007
|
-
channel: Channel<T>
|
|
2008
|
-
|
|
2009
|
-
/**
|
|
2010
|
-
* The direction of this channel reference
|
|
2011
|
-
*/
|
|
2012
|
-
direction: 'send' | 'receive' | 'both'
|
|
2013
|
-
|
|
2014
|
-
// Channel methods
|
|
2015
|
-
send(value: T): Promise<void>
|
|
2016
|
-
receive(): Promise<T>
|
|
2017
|
-
receiveWithOk(): Promise<ChannelReceiveResult<T>>
|
|
2018
|
-
close(): void
|
|
2019
|
-
canSendNonBlocking(): boolean
|
|
2020
|
-
canReceiveNonBlocking(): boolean
|
|
2021
|
-
selectSend(value: T, id: number): Promise<SelectResult<boolean>>
|
|
2022
|
-
selectReceive(id: number): Promise<SelectResult<T>>
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
/**
|
|
2026
|
-
* A bidirectional channel reference.
|
|
2027
|
-
*/
|
|
2028
|
-
export class BidirectionalChannelRef<T> implements ChannelRef<T> {
|
|
2029
|
-
direction: 'both' = 'both'
|
|
2030
|
-
|
|
2031
|
-
constructor(public channel: Channel<T>) {}
|
|
2032
|
-
|
|
2033
|
-
// Delegate all methods to the underlying channel
|
|
2034
|
-
send(value: T): Promise<void> {
|
|
2035
|
-
return this.channel.send(value)
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
receive(): Promise<T> {
|
|
2039
|
-
return this.channel.receive()
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
2043
|
-
return this.channel.receiveWithOk()
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
close(): void {
|
|
2047
|
-
this.channel.close()
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
canSendNonBlocking(): boolean {
|
|
2051
|
-
return this.channel.canSendNonBlocking()
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
canReceiveNonBlocking(): boolean {
|
|
2055
|
-
return this.channel.canReceiveNonBlocking()
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
2059
|
-
return this.channel.selectSend(value, id)
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
selectReceive(id: number): Promise<SelectResult<T>> {
|
|
2063
|
-
return this.channel.selectReceive(id)
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
/**
|
|
2068
|
-
* A send-only channel reference.
|
|
2069
|
-
*/
|
|
2070
|
-
export class SendOnlyChannelRef<T> implements ChannelRef<T> {
|
|
2071
|
-
direction: 'send' = 'send'
|
|
2072
|
-
|
|
2073
|
-
constructor(public channel: Channel<T>) {}
|
|
2074
|
-
|
|
2075
|
-
// Allow send operations
|
|
2076
|
-
send(value: T): Promise<void> {
|
|
2077
|
-
return this.channel.send(value)
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
// Allow close operations
|
|
2081
|
-
close(): void {
|
|
2082
|
-
this.channel.close()
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
canSendNonBlocking(): boolean {
|
|
2086
|
-
return this.channel.canSendNonBlocking()
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
2090
|
-
return this.channel.selectSend(value, id)
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
// Disallow receive operations
|
|
2094
|
-
receive(): Promise<T> {
|
|
2095
|
-
throw new Error('Cannot receive from send-only channel')
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
2099
|
-
throw new Error('Cannot receive from send-only channel')
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
canReceiveNonBlocking(): boolean {
|
|
2103
|
-
return false
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
selectReceive(id: number): Promise<SelectResult<T>> {
|
|
2107
|
-
throw new Error('Cannot receive from send-only channel')
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
/**
|
|
2112
|
-
* A receive-only channel reference.
|
|
2113
|
-
*/
|
|
2114
|
-
export class ReceiveOnlyChannelRef<T> implements ChannelRef<T> {
|
|
2115
|
-
direction: 'receive' = 'receive'
|
|
2116
|
-
|
|
2117
|
-
constructor(public channel: Channel<T>) {}
|
|
2118
|
-
|
|
2119
|
-
// Allow receive operations
|
|
2120
|
-
receive(): Promise<T> {
|
|
2121
|
-
return this.channel.receive()
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
2125
|
-
return this.channel.receiveWithOk()
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
canReceiveNonBlocking(): boolean {
|
|
2129
|
-
return this.channel.canReceiveNonBlocking()
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
selectReceive(id: number): Promise<SelectResult<T>> {
|
|
2133
|
-
return this.channel.selectReceive(id)
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
// Disallow send operations
|
|
2137
|
-
send(value: T): Promise<void> {
|
|
2138
|
-
throw new Error('Cannot send to receive-only channel')
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
// Disallow close operations
|
|
2142
|
-
close(): void {
|
|
2143
|
-
throw new Error('Cannot close receive-only channel')
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
canSendNonBlocking(): boolean {
|
|
2147
|
-
return false
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
2151
|
-
throw new Error('Cannot send to receive-only channel')
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
/**
|
|
2156
|
-
* Creates a new channel reference with the specified direction.
|
|
2157
|
-
*/
|
|
2158
|
-
export function makeChannelRef<T>(
|
|
2159
|
-
channel: Channel<T>,
|
|
2160
|
-
direction: 'send' | 'receive' | 'both',
|
|
2161
|
-
): ChannelRef<T> {
|
|
2162
|
-
switch (direction) {
|
|
2163
|
-
case 'send':
|
|
2164
|
-
return new SendOnlyChannelRef<T>(channel)
|
|
2165
|
-
case 'receive':
|
|
2166
|
-
return new ReceiveOnlyChannelRef<T>(channel)
|
|
2167
|
-
default: // 'both'
|
|
2168
|
-
return new BidirectionalChannelRef<T>(channel)
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
/**
|
|
2173
|
-
* Represents a case in a select statement.
|
|
2174
|
-
*/
|
|
2175
|
-
export interface SelectCase<T> {
|
|
2176
|
-
id: number
|
|
2177
|
-
isSend: boolean // true for send, false for receive
|
|
2178
|
-
channel: Channel<any> | ChannelRef<any> | null // Allow null and ChannelRef
|
|
2179
|
-
value?: any // Value to send for send cases
|
|
2180
|
-
// Optional handlers for when this case is selected
|
|
2181
|
-
onSelected?: (result: SelectResult<T>) => Promise<void>
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
/**
|
|
2185
|
-
* Helper for 'select' statements. Takes an array of select cases
|
|
2186
|
-
* and resolves when one of them completes, following Go's select rules.
|
|
2187
|
-
*
|
|
2188
|
-
* @param cases Array of SelectCase objects
|
|
2189
|
-
* @param hasDefault Whether there is a default case
|
|
2190
|
-
* @returns A promise that resolves with the result of the selected case
|
|
2191
|
-
*/
|
|
2192
|
-
export async function selectStatement<T>(
|
|
2193
|
-
cases: SelectCase<T>[],
|
|
2194
|
-
hasDefault: boolean = false,
|
|
2195
|
-
): Promise<void> {
|
|
2196
|
-
if (cases.length === 0 && !hasDefault) {
|
|
2197
|
-
// Go spec: If there are no cases, the select statement blocks forever.
|
|
2198
|
-
// Emulate blocking forever with a promise that never resolves.
|
|
2199
|
-
return new Promise<void>(() => {}) // Promise never resolves
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
// 1. Check for ready (non-blocking) operations
|
|
2203
|
-
const readyCases: SelectCase<T>[] = []
|
|
2204
|
-
for (const caseObj of cases) {
|
|
2205
|
-
if (caseObj.id === -1) {
|
|
2206
|
-
// Skip default case in this check
|
|
2207
|
-
continue
|
|
2208
|
-
}
|
|
2209
|
-
// Add check for channel existence
|
|
2210
|
-
if (caseObj.channel) {
|
|
2211
|
-
if (caseObj.isSend && caseObj.channel.canSendNonBlocking()) {
|
|
2212
|
-
readyCases.push(caseObj)
|
|
2213
|
-
} else if (!caseObj.isSend && caseObj.channel.canReceiveNonBlocking()) {
|
|
2214
|
-
readyCases.push(caseObj)
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
if (readyCases.length > 0) {
|
|
2220
|
-
// If one or more cases are ready, choose one pseudo-randomly
|
|
2221
|
-
const selectedCase =
|
|
2222
|
-
readyCases[Math.floor(Math.random() * readyCases.length)]
|
|
2223
|
-
|
|
2224
|
-
// Execute the selected operation and its onSelected handler
|
|
2225
|
-
// Add check for channel existence
|
|
2226
|
-
if (selectedCase.channel) {
|
|
2227
|
-
if (selectedCase.isSend) {
|
|
2228
|
-
const result = await selectedCase.channel.selectSend(
|
|
2229
|
-
selectedCase.value,
|
|
2230
|
-
selectedCase.id,
|
|
2231
|
-
)
|
|
2232
|
-
if (selectedCase.onSelected) {
|
|
2233
|
-
await selectedCase.onSelected(result as SelectResult<T>) // Await the handler
|
|
2234
|
-
}
|
|
2235
|
-
} else {
|
|
2236
|
-
const result = await selectedCase.channel.selectReceive(selectedCase.id)
|
|
2237
|
-
if (selectedCase.onSelected) {
|
|
2238
|
-
await selectedCase.onSelected(result) // Await the handler
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
} else {
|
|
2242
|
-
// This case should ideally not happen if channel is required for non-default cases
|
|
2243
|
-
console.error('Selected case without a channel:', selectedCase)
|
|
2244
|
-
}
|
|
2245
|
-
return // Return after executing a ready case
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
// 2. If no operations are ready and there's a default case, select default
|
|
2249
|
-
if (hasDefault) {
|
|
2250
|
-
// Find the default case (it will have id -1)
|
|
2251
|
-
const defaultCase = cases.find((c) => c.id === -1)
|
|
2252
|
-
if (defaultCase && defaultCase.onSelected) {
|
|
2253
|
-
// Execute the onSelected handler for the default case
|
|
2254
|
-
await defaultCase.onSelected({
|
|
2255
|
-
value: undefined,
|
|
2256
|
-
ok: false,
|
|
2257
|
-
id: -1,
|
|
2258
|
-
} as SelectResult<T>) // Await the handler
|
|
2259
|
-
}
|
|
2260
|
-
return // Return after executing the default case
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
// 3. If no operations are ready and no default case, block until one is ready
|
|
2264
|
-
// Use Promise.race on the blocking promises
|
|
2265
|
-
const blockingPromises = cases
|
|
2266
|
-
.filter((c) => c.id !== -1)
|
|
2267
|
-
.map((caseObj) => {
|
|
2268
|
-
// Exclude default case
|
|
2269
|
-
// Add check for channel existence (though it should always exist here)
|
|
2270
|
-
if (caseObj.channel) {
|
|
2271
|
-
if (caseObj.isSend) {
|
|
2272
|
-
return caseObj.channel.selectSend(caseObj.value, caseObj.id)
|
|
2273
|
-
} else {
|
|
2274
|
-
return caseObj.channel.selectReceive(caseObj.id)
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
// Return a promise that never resolves if channel is somehow missing
|
|
2278
|
-
return new Promise<SelectResult<any>>(() => {})
|
|
2279
|
-
})
|
|
2280
|
-
|
|
2281
|
-
const result = await Promise.race(blockingPromises)
|
|
2282
|
-
// Execute onSelected handler for the selected case
|
|
2283
|
-
const selectedCase = cases.find((c) => c.id === result.id)
|
|
2284
|
-
if (selectedCase && selectedCase.onSelected) {
|
|
2285
|
-
await selectedCase.onSelected(result) // Await the handler
|
|
2286
|
-
}
|
|
2287
|
-
// No explicit return needed here, as the function will implicitly return after the await
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
/**
|
|
2291
|
-
* Creates a new channel with the specified buffer size and zero value.
|
|
2292
|
-
* @param bufferSize The size of the channel buffer. If 0, creates an unbuffered channel.
|
|
2293
|
-
* @param zeroValue The zero value for the channel's element type.
|
|
2294
|
-
* @param direction Optional direction for the channel. Default is 'both' (bidirectional).
|
|
2295
|
-
* @returns A new channel instance or channel reference.
|
|
2296
|
-
*/
|
|
2297
|
-
export const makeChannel = <T>(
|
|
2298
|
-
bufferSize: number,
|
|
2299
|
-
zeroValue: T,
|
|
2300
|
-
direction: 'send' | 'receive' | 'both' = 'both',
|
|
2301
|
-
): Channel<T> | ChannelRef<T> => {
|
|
2302
|
-
const channel = new BufferedChannel<T>(bufferSize, zeroValue)
|
|
2303
|
-
|
|
2304
|
-
// Wrap the channel with the appropriate ChannelRef based on direction
|
|
2305
|
-
if (direction === 'send') {
|
|
2306
|
-
return new SendOnlyChannelRef<T>(channel) as ChannelRef<T>
|
|
2307
|
-
} else if (direction === 'receive') {
|
|
2308
|
-
return new ReceiveOnlyChannelRef<T>(channel) as ChannelRef<T>
|
|
2309
|
-
} else {
|
|
2310
|
-
return channel
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
/**
|
|
2315
|
-
* DisposableStack manages synchronous disposable resources, mimicking Go's defer behavior.
|
|
2316
|
-
* Functions added via `defer` are executed in LIFO order when the stack is disposed.
|
|
2317
|
-
* Implements the `Disposable` interface for use with `using` declarations.
|
|
2318
|
-
*/
|
|
2319
|
-
export class DisposableStack implements Disposable {
|
|
2320
|
-
private stack: (() => void)[] = []
|
|
2321
|
-
|
|
2322
|
-
/**
|
|
2323
|
-
* Adds a function to be executed when the stack is disposed.
|
|
2324
|
-
* @param fn The function to defer.
|
|
2325
|
-
*/
|
|
2326
|
-
defer(fn: () => void): void {
|
|
2327
|
-
this.stack.push(fn)
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
/**
|
|
2331
|
-
* Disposes of the resources in the stack by executing the deferred functions
|
|
2332
|
-
* in Last-In, First-Out (LIFO) order.
|
|
2333
|
-
* If a deferred function throws an error, disposal stops, and the error is rethrown,
|
|
2334
|
-
* similar to Go's panic behavior during defer execution.
|
|
2335
|
-
*/
|
|
2336
|
-
[Symbol.dispose](): void {
|
|
2337
|
-
// Emulate Go: if a deferred throws, stop and rethrow
|
|
2338
|
-
while (this.stack.length) {
|
|
2339
|
-
const fn = this.stack.pop()!
|
|
2340
|
-
fn()
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
/**
|
|
2346
|
-
* AsyncDisposableStack manages asynchronous disposable resources, mimicking Go's defer behavior.
|
|
2347
|
-
* Functions added via `defer` are executed sequentially in LIFO order when the stack is disposed.
|
|
2348
|
-
* Implements the `AsyncDisposable` interface for use with `await using` declarations.
|
|
2349
|
-
*/
|
|
2350
|
-
export class AsyncDisposableStack implements AsyncDisposable {
|
|
2351
|
-
private stack: (() => Promise<void> | void)[] = []
|
|
2352
|
-
|
|
2353
|
-
/**
|
|
2354
|
-
* Adds a synchronous or asynchronous function to be executed when the stack is disposed.
|
|
2355
|
-
* @param fn The function to defer. Can return void or a Promise<void>.
|
|
2356
|
-
*/
|
|
2357
|
-
defer(fn: () => Promise<void> | void): void {
|
|
2358
|
-
this.stack.push(fn)
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
/**
|
|
2362
|
-
* Asynchronously disposes of the resources in the stack by executing the deferred functions
|
|
2363
|
-
* sequentially in Last-In, First-Out (LIFO) order. It awaits each function if it returns a promise.
|
|
2364
|
-
*/
|
|
2365
|
-
async [Symbol.asyncDispose](): Promise<void> {
|
|
2366
|
-
// Execute in LIFO order, awaiting each potentially async function
|
|
2367
|
-
for (let i = this.stack.length - 1; i >= 0; --i) {
|
|
2368
|
-
await this.stack[i]()
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
/**
|
|
2374
|
-
* Implementation of Go's built-in println function
|
|
2375
|
-
* @param args Arguments to print
|
|
2376
|
-
*/
|
|
2377
|
-
export function println(...args: any[]): void {
|
|
2378
|
-
console.log(...args)
|
|
2379
|
-
}
|