goscript 0.0.26 → 0.0.29
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 +4 -4
- package/cmd/goscript/cmd_compile.go +0 -3
- package/cmd/goscript/deps.go +11 -0
- package/compiler/analysis.go +298 -55
- package/compiler/assignment.go +2 -2
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +200 -68
- package/compiler/compiler_test.go +17 -24
- package/compiler/composite-lit.go +32 -8
- package/compiler/decl.go +6 -6
- package/compiler/expr-call.go +170 -15
- package/compiler/expr-selector.go +100 -0
- package/compiler/expr.go +1 -1
- package/compiler/protobuf.go +557 -0
- package/compiler/spec-struct.go +4 -0
- package/compiler/spec-value.go +89 -10
- package/compiler/spec.go +254 -1
- package/compiler/stmt-assign.go +35 -0
- package/compiler/type-assert.go +87 -0
- package/compiler/type.go +4 -1
- package/dist/gs/builtin/builtin.d.ts +20 -1
- package/dist/gs/builtin/builtin.js +95 -4
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js +21 -2
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/errors/errors.d.ts +5 -6
- package/dist/gs/errors/errors.js.map +1 -1
- package/dist/gs/internal/oserror/errors.d.ts +6 -0
- package/dist/gs/internal/oserror/errors.js +7 -0
- package/dist/gs/internal/oserror/errors.js.map +1 -0
- package/dist/gs/internal/oserror/index.d.ts +1 -0
- package/dist/gs/internal/oserror/index.js +2 -0
- package/dist/gs/internal/oserror/index.js.map +1 -0
- package/dist/gs/io/fs/format.d.ts +3 -0
- package/dist/gs/io/fs/format.js +56 -0
- package/dist/gs/io/fs/format.js.map +1 -0
- package/dist/gs/io/fs/fs.d.ts +79 -0
- package/dist/gs/io/fs/fs.js +200 -0
- package/dist/gs/io/fs/fs.js.map +1 -0
- package/dist/gs/io/fs/glob.d.ts +10 -0
- package/dist/gs/io/fs/glob.js +141 -0
- package/dist/gs/io/fs/glob.js.map +1 -0
- package/dist/gs/io/fs/index.d.ts +8 -0
- package/dist/gs/io/fs/index.js +9 -0
- package/dist/gs/io/fs/index.js.map +1 -0
- package/dist/gs/io/fs/readdir.d.ts +7 -0
- package/dist/gs/io/fs/readdir.js +152 -0
- package/dist/gs/io/fs/readdir.js.map +1 -0
- package/dist/gs/io/fs/readfile.d.ts +6 -0
- package/dist/gs/io/fs/readfile.js +118 -0
- package/dist/gs/io/fs/readfile.js.map +1 -0
- package/dist/gs/io/fs/stat.d.ts +6 -0
- package/dist/gs/io/fs/stat.js +87 -0
- package/dist/gs/io/fs/stat.js.map +1 -0
- package/dist/gs/io/fs/sub.d.ts +6 -0
- package/dist/gs/io/fs/sub.js +172 -0
- package/dist/gs/io/fs/sub.js.map +1 -0
- package/dist/gs/io/fs/walk.d.ts +7 -0
- package/dist/gs/io/fs/walk.js +76 -0
- package/dist/gs/io/fs/walk.js.map +1 -0
- package/dist/gs/io/index.d.ts +1 -0
- package/dist/gs/io/index.js +2 -0
- package/dist/gs/io/index.js.map +1 -0
- package/dist/gs/io/io.d.ts +107 -0
- package/dist/gs/io/io.js +385 -0
- package/dist/gs/io/io.js.map +1 -0
- package/dist/gs/path/index.d.ts +2 -0
- package/dist/gs/path/index.js +3 -0
- package/dist/gs/path/index.js.map +1 -0
- package/dist/gs/path/match.d.ts +6 -0
- package/dist/gs/path/match.js +281 -0
- package/dist/gs/path/match.js.map +1 -0
- package/dist/gs/path/path.d.ts +7 -0
- package/dist/gs/path/path.js +256 -0
- package/dist/gs/path/path.js.map +1 -0
- package/dist/gs/strings/builder.d.ts +18 -0
- package/dist/gs/strings/builder.js +205 -0
- package/dist/gs/strings/builder.js.map +1 -0
- package/dist/gs/strings/clone.d.ts +1 -0
- package/dist/gs/strings/clone.js +16 -0
- package/dist/gs/strings/clone.js.map +1 -0
- package/dist/gs/strings/compare.d.ts +1 -0
- package/dist/gs/strings/compare.js +14 -0
- package/dist/gs/strings/compare.js.map +1 -0
- package/dist/gs/strings/index.d.ts +2 -0
- package/dist/gs/strings/index.js +3 -0
- package/dist/gs/strings/index.js.map +1 -0
- package/dist/gs/strings/iter.d.ts +8 -0
- package/dist/gs/strings/iter.js +160 -0
- package/dist/gs/strings/iter.js.map +1 -0
- package/dist/gs/strings/reader.d.ts +34 -0
- package/dist/gs/strings/reader.js +418 -0
- package/dist/gs/strings/reader.js.map +1 -0
- package/dist/gs/strings/replace.d.ts +106 -0
- package/dist/gs/strings/replace.js +1136 -0
- package/dist/gs/strings/replace.js.map +1 -0
- package/dist/gs/strings/search.d.ts +24 -0
- package/dist/gs/strings/search.js +169 -0
- package/dist/gs/strings/search.js.map +1 -0
- package/dist/gs/strings/strings.d.ts +47 -0
- package/dist/gs/strings/strings.js +418 -0
- package/dist/gs/strings/strings.js.map +1 -0
- package/dist/gs/stringslite/index.d.ts +1 -0
- package/dist/gs/stringslite/index.js +2 -0
- package/dist/gs/stringslite/index.js.map +1 -0
- package/dist/gs/stringslite/strings.d.ts +11 -0
- package/dist/gs/stringslite/strings.js +67 -0
- package/dist/gs/stringslite/strings.js.map +1 -0
- package/dist/gs/sync/index.d.ts +1 -0
- package/dist/gs/sync/index.js +2 -0
- package/dist/gs/sync/index.js.map +1 -0
- package/dist/gs/sync/sync.d.ts +79 -0
- package/dist/gs/sync/sync.js +392 -0
- package/dist/gs/sync/sync.js.map +1 -0
- package/dist/gs/time/time.d.ts +11 -2
- package/dist/gs/time/time.js +337 -12
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/index.d.ts +1 -0
- package/dist/gs/unicode/index.js +2 -0
- package/dist/gs/unicode/index.js.map +1 -0
- package/dist/gs/unicode/unicode.d.ts +105 -0
- package/dist/gs/unicode/unicode.js +332 -0
- package/dist/gs/unicode/unicode.js.map +1 -0
- package/dist/gs/unicode/utf8/index.d.ts +1 -0
- package/dist/gs/unicode/utf8/index.js +3 -0
- package/dist/gs/unicode/utf8/index.js.map +1 -0
- package/dist/gs/unicode/utf8/utf8.d.ts +20 -0
- package/dist/gs/unicode/utf8/utf8.js +196 -0
- package/dist/gs/unicode/utf8/utf8.js.map +1 -0
- package/dist/gs/unsafe/index.d.ts +1 -0
- package/dist/gs/unsafe/index.js +2 -0
- package/dist/gs/unsafe/index.js.map +1 -0
- package/dist/gs/unsafe/unsafe.d.ts +11 -0
- package/dist/gs/unsafe/unsafe.js +44 -0
- package/dist/gs/unsafe/unsafe.js.map +1 -0
- package/go.mod +2 -1
- package/go.sum +6 -2
- package/gs/README.md +6 -0
- package/gs/builtin/builtin.ts +171 -0
- package/gs/builtin/channel.ts +683 -0
- package/gs/builtin/defer.ts +58 -0
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/io.ts +22 -0
- package/gs/builtin/map.ts +50 -0
- package/gs/builtin/slice.ts +1030 -0
- package/gs/builtin/type.ts +1106 -0
- package/gs/builtin/varRef.ts +25 -0
- package/gs/cmp/godoc.txt +8 -0
- package/gs/cmp/index.ts +29 -0
- package/gs/context/context.ts +401 -0
- package/gs/context/godoc.txt +69 -0
- package/gs/context/index.ts +1 -0
- package/gs/errors/errors.ts +223 -0
- package/gs/errors/godoc.txt +63 -0
- package/gs/errors/index.ts +1 -0
- package/gs/internal/goarch/godoc.txt +39 -0
- package/gs/internal/goarch/index.ts +18 -0
- package/gs/internal/oserror/errors.ts +14 -0
- package/gs/internal/oserror/index.ts +1 -0
- package/gs/io/fs/format.ts +65 -0
- package/gs/io/fs/fs.ts +359 -0
- package/gs/io/fs/glob.ts +167 -0
- package/gs/io/fs/godoc.txt +35 -0
- package/gs/io/fs/index.ts +8 -0
- package/gs/io/fs/readdir.ts +126 -0
- package/gs/io/fs/readfile.ts +77 -0
- package/gs/io/fs/stat.ts +38 -0
- package/gs/io/fs/sub.ts +208 -0
- package/gs/io/fs/walk.ts +89 -0
- package/gs/io/godoc.txt +61 -0
- package/gs/io/index.ts +1 -0
- package/gs/io/io.go +75 -0
- package/gs/io/io.ts +546 -0
- package/gs/iter/godoc.txt +203 -0
- package/gs/iter/index.ts +1 -0
- package/gs/iter/iter.ts +117 -0
- package/gs/math/bits/index.ts +356 -0
- package/gs/math/godoc.txt +76 -0
- package/gs/path/index.ts +2 -0
- package/gs/path/match.ts +307 -0
- package/gs/path/path.ts +301 -0
- package/gs/runtime/godoc.txt +331 -0
- package/gs/runtime/index.ts +1 -0
- package/gs/runtime/runtime.ts +178 -0
- package/gs/slices/godoc.txt +44 -0
- package/gs/slices/index.ts +1 -0
- package/gs/slices/slices.ts +22 -0
- package/gs/strings/builder.test.ts +121 -0
- package/gs/strings/builder.ts +223 -0
- package/gs/strings/clone.test.ts +43 -0
- package/gs/strings/clone.ts +17 -0
- package/gs/strings/compare.test.ts +84 -0
- package/gs/strings/compare.ts +13 -0
- package/gs/strings/godoc.txt +66 -0
- package/gs/strings/index.ts +2 -0
- package/gs/strings/iter.test.ts +343 -0
- package/gs/strings/iter.ts +171 -0
- package/gs/strings/reader.test.ts +242 -0
- package/gs/strings/reader.ts +451 -0
- package/gs/strings/replace.test.ts +181 -0
- package/gs/strings/replace.ts +1310 -0
- package/gs/strings/search.test.ts +214 -0
- package/gs/strings/search.ts +213 -0
- package/gs/strings/strings.test.ts +477 -0
- package/gs/strings/strings.ts +510 -0
- package/gs/stringslite/godoc.txt +17 -0
- package/gs/stringslite/index.ts +1 -0
- package/gs/stringslite/strings.ts +82 -0
- package/gs/sync/godoc.txt +21 -0
- package/gs/sync/index.ts +1 -0
- package/gs/sync/sync.go +64 -0
- package/gs/sync/sync.ts +449 -0
- package/gs/time/godoc.txt +116 -0
- package/gs/time/index.ts +1 -0
- package/gs/time/time.ts +585 -0
- package/gs/unicode/godoc.txt +52 -0
- package/gs/unicode/index.ts +1 -0
- package/gs/unicode/unicode.go +38 -0
- package/gs/unicode/unicode.ts +418 -0
- package/gs/unicode/utf8/godoc.txt +22 -0
- package/gs/unicode/utf8/index.ts +2 -0
- package/gs/unicode/utf8/utf8.ts +227 -0
- package/gs/unsafe/godoc.txt +19 -0
- package/gs/unsafe/index.ts +1 -0
- package/gs/unsafe/unsafe.test.ts +68 -0
- package/gs/unsafe/unsafe.ts +77 -0
- package/package.json +4 -3
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the result of a channel receive operation with 'ok' value
|
|
3
|
+
*/
|
|
4
|
+
export interface ChannelReceiveResult<T> {
|
|
5
|
+
value: T // Should be T | ZeroValue<T>
|
|
6
|
+
ok: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a result from a select operation
|
|
11
|
+
*/
|
|
12
|
+
export interface SelectResult<T> {
|
|
13
|
+
value: T // Should be T | ZeroValue<T>
|
|
14
|
+
ok: boolean
|
|
15
|
+
id: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Represents a Go channel in TypeScript.
|
|
20
|
+
* Supports asynchronous sending and receiving of values.
|
|
21
|
+
*/
|
|
22
|
+
export interface Channel<T> {
|
|
23
|
+
/**
|
|
24
|
+
* Sends a value to the channel.
|
|
25
|
+
* Returns a promise that resolves when the value is accepted by the channel.
|
|
26
|
+
* @param value The value to send.
|
|
27
|
+
*/
|
|
28
|
+
send(value: T): Promise<void>
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Receives a value from the channel.
|
|
32
|
+
* Returns a promise that resolves with the received value.
|
|
33
|
+
* If the channel is closed, it throws an error.
|
|
34
|
+
*/
|
|
35
|
+
receive(): Promise<T>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Receives a value from the channel along with a boolean indicating
|
|
39
|
+
* whether the channel is still open.
|
|
40
|
+
* Returns a promise that resolves with {value, ok}.
|
|
41
|
+
* - If channel is open and has data: {value: <data>, ok: true}
|
|
42
|
+
* - If channel is closed and empty: {value: <zero value>, ok: false}
|
|
43
|
+
* - If channel is closed but has remaining buffered data: {value: <data>, ok: true}
|
|
44
|
+
*/
|
|
45
|
+
receiveWithOk(): Promise<ChannelReceiveResult<T>>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Closes the channel.
|
|
49
|
+
* No more values can be sent to a closed channel.
|
|
50
|
+
* Receive operations on a closed channel return the zero value and ok=false.
|
|
51
|
+
*/
|
|
52
|
+
close(): void
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Used in select statements to create a receive operation promise.
|
|
56
|
+
* @param id An identifier for this case in the select statement
|
|
57
|
+
* @returns Promise that resolves when this case is selected
|
|
58
|
+
*/
|
|
59
|
+
selectReceive(id: number): Promise<SelectResult<T>>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Used in select statements to create a send operation promise.
|
|
63
|
+
* @param value The value to send
|
|
64
|
+
* @param id An identifier for this case in the select statement
|
|
65
|
+
* @returns Promise that resolves when this case is selected
|
|
66
|
+
*/
|
|
67
|
+
selectSend(value: T, id: number): Promise<SelectResult<boolean>>
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checks if the channel has data ready to be received without blocking.
|
|
71
|
+
* Used for non-blocking select operations.
|
|
72
|
+
*/
|
|
73
|
+
canReceiveNonBlocking(): boolean
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Checks if the channel can accept a send operation without blocking.
|
|
77
|
+
* Used for non-blocking select operations.
|
|
78
|
+
*/
|
|
79
|
+
canSendNonBlocking(): boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Represents a case in a select statement.
|
|
84
|
+
*/
|
|
85
|
+
export interface SelectCase<T> {
|
|
86
|
+
id: number
|
|
87
|
+
isSend: boolean // true for send, false for receive
|
|
88
|
+
channel: Channel<any> | ChannelRef<any> | null // Allow null and ChannelRef
|
|
89
|
+
value?: any // Value to send for send cases
|
|
90
|
+
// Optional handlers for when this case is selected
|
|
91
|
+
onSelected?: (result: SelectResult<T>) => Promise<void>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Helper for 'select' statements. Takes an array of select cases
|
|
96
|
+
* and resolves when one of them completes, following Go's select rules.
|
|
97
|
+
*
|
|
98
|
+
* @param cases Array of SelectCase objects
|
|
99
|
+
* @param hasDefault Whether there is a default case
|
|
100
|
+
* @returns A promise that resolves with the result of the selected case
|
|
101
|
+
*/
|
|
102
|
+
export async function selectStatement<T>(
|
|
103
|
+
cases: SelectCase<T>[],
|
|
104
|
+
hasDefault: boolean = false,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
if (cases.length === 0 && !hasDefault) {
|
|
107
|
+
// Go spec: If there are no cases, the select statement blocks forever.
|
|
108
|
+
// Emulate blocking forever with a promise that never resolves.
|
|
109
|
+
return new Promise<void>(() => {}) // Promise never resolves
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 1. Check for ready (non-blocking) operations
|
|
113
|
+
const readyCases: SelectCase<T>[] = []
|
|
114
|
+
for (const caseObj of cases) {
|
|
115
|
+
if (caseObj.id === -1) {
|
|
116
|
+
// Skip default case in this check
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
// Skip nil channels - they are never ready in Go
|
|
120
|
+
if (caseObj.channel === null) {
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
if (caseObj.channel) {
|
|
124
|
+
if (caseObj.isSend && caseObj.channel.canSendNonBlocking()) {
|
|
125
|
+
readyCases.push(caseObj)
|
|
126
|
+
} else if (!caseObj.isSend && caseObj.channel.canReceiveNonBlocking()) {
|
|
127
|
+
readyCases.push(caseObj)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (readyCases.length > 0) {
|
|
133
|
+
// If one or more cases are ready, choose one pseudo-randomly
|
|
134
|
+
const selectedCase =
|
|
135
|
+
readyCases[Math.floor(Math.random() * readyCases.length)]
|
|
136
|
+
|
|
137
|
+
// Execute the selected operation and its onSelected handler
|
|
138
|
+
// Add check for channel existence
|
|
139
|
+
if (selectedCase.channel) {
|
|
140
|
+
if (selectedCase.isSend) {
|
|
141
|
+
const result = await selectedCase.channel.selectSend(
|
|
142
|
+
selectedCase.value,
|
|
143
|
+
selectedCase.id,
|
|
144
|
+
)
|
|
145
|
+
if (selectedCase.onSelected) {
|
|
146
|
+
await selectedCase.onSelected(result as SelectResult<T>) // Await the handler
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
const result = await selectedCase.channel.selectReceive(selectedCase.id)
|
|
150
|
+
if (selectedCase.onSelected) {
|
|
151
|
+
await selectedCase.onSelected(result) // Await the handler
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
// This case should ideally not happen if channel is required for non-default cases
|
|
156
|
+
console.error('Selected case without a channel:', selectedCase)
|
|
157
|
+
}
|
|
158
|
+
return // Return after executing a ready case
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 2. If no operations are ready and there's a default case, select default
|
|
162
|
+
if (hasDefault) {
|
|
163
|
+
// Find the default case (it will have id -1)
|
|
164
|
+
const defaultCase = cases.find((c) => c.id === -1)
|
|
165
|
+
if (defaultCase && defaultCase.onSelected) {
|
|
166
|
+
// Execute the onSelected handler for the default case
|
|
167
|
+
await defaultCase.onSelected({
|
|
168
|
+
value: undefined,
|
|
169
|
+
ok: false,
|
|
170
|
+
id: -1,
|
|
171
|
+
} as SelectResult<T>) // Await the handler
|
|
172
|
+
}
|
|
173
|
+
return // Return after executing the default case
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 3. If no operations are ready and no default case, block until one is ready
|
|
177
|
+
// Use Promise.race on the blocking promises
|
|
178
|
+
const blockingPromises = cases
|
|
179
|
+
.filter((c) => c.id !== -1) // Exclude default case
|
|
180
|
+
.filter((c) => c.channel !== null) // Exclude nil channels (they would block forever)
|
|
181
|
+
.map((caseObj) => {
|
|
182
|
+
// At this point caseObj.channel is guaranteed to be non-null
|
|
183
|
+
if (caseObj.isSend) {
|
|
184
|
+
return caseObj.channel!.selectSend(caseObj.value, caseObj.id)
|
|
185
|
+
} else {
|
|
186
|
+
return caseObj.channel!.selectReceive(caseObj.id)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// If all non-default cases have nil channels, we effectively block forever
|
|
191
|
+
if (blockingPromises.length === 0) {
|
|
192
|
+
// No valid channels to operate on, block forever (unless there's a default)
|
|
193
|
+
return new Promise<void>(() => {}) // Promise never resolves
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await Promise.race(blockingPromises)
|
|
197
|
+
// Execute onSelected handler for the selected case
|
|
198
|
+
const selectedCase = cases.find((c) => c.id === result.id)
|
|
199
|
+
if (selectedCase && selectedCase.onSelected) {
|
|
200
|
+
await selectedCase.onSelected(result) // Await the handler
|
|
201
|
+
}
|
|
202
|
+
// No explicit return needed here, as the function will implicitly return after the await
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Helper function for channel send operations that handles nil channels correctly.
|
|
207
|
+
* In Go, sending to a nil channel blocks forever.
|
|
208
|
+
* @param channel The channel to send to (can be null)
|
|
209
|
+
* @param value The value to send
|
|
210
|
+
* @returns Promise that never resolves if channel is null, otherwise delegates to channel.send()
|
|
211
|
+
*/
|
|
212
|
+
export async function chanSend<T>(
|
|
213
|
+
channel: Channel<T> | ChannelRef<T> | null,
|
|
214
|
+
value: T,
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
if (channel === null) {
|
|
217
|
+
// In Go, sending to a nil channel blocks forever
|
|
218
|
+
return new Promise<void>(() => {}) // Promise that never resolves
|
|
219
|
+
}
|
|
220
|
+
return channel.send(value)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Helper function for channel receive operations that handles nil channels correctly.
|
|
225
|
+
* In Go, receiving from a nil channel blocks forever.
|
|
226
|
+
* @param channel The channel to receive from (can be null)
|
|
227
|
+
* @returns Promise that never resolves if channel is null, otherwise delegates to channel.receive()
|
|
228
|
+
*/
|
|
229
|
+
export async function chanRecv<T>(
|
|
230
|
+
channel: Channel<T> | ChannelRef<T> | null,
|
|
231
|
+
): Promise<T> {
|
|
232
|
+
if (channel === null) {
|
|
233
|
+
// In Go, receiving from a nil channel blocks forever
|
|
234
|
+
return new Promise<T>(() => {}) // Promise that never resolves
|
|
235
|
+
}
|
|
236
|
+
return channel.receive()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Helper function for channel receive operations with ok value that handles nil channels correctly.
|
|
241
|
+
* In Go, receiving from a nil channel blocks forever.
|
|
242
|
+
* @param channel The channel to receive from (can be null)
|
|
243
|
+
* @returns Promise that never resolves if channel is null, otherwise delegates to channel.receiveWithOk()
|
|
244
|
+
*/
|
|
245
|
+
export async function chanRecvWithOk<T>(
|
|
246
|
+
channel: Channel<T> | ChannelRef<T> | null,
|
|
247
|
+
): Promise<ChannelReceiveResult<T>> {
|
|
248
|
+
if (channel === null) {
|
|
249
|
+
// In Go, receiving from a nil channel blocks forever
|
|
250
|
+
return new Promise<ChannelReceiveResult<T>>(() => {}) // Promise that never resolves
|
|
251
|
+
}
|
|
252
|
+
return channel.receiveWithOk()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Creates a new channel with the specified buffer size and zero value.
|
|
257
|
+
* @param bufferSize The size of the channel buffer. If 0, creates an unbuffered channel.
|
|
258
|
+
* @param zeroValue The zero value for the channel's element type.
|
|
259
|
+
* @param direction Optional direction for the channel. Default is 'both' (bidirectional).
|
|
260
|
+
* @returns A new channel instance or channel reference.
|
|
261
|
+
*/
|
|
262
|
+
export const makeChannel = <T>(
|
|
263
|
+
bufferSize: number,
|
|
264
|
+
zeroValue: T,
|
|
265
|
+
direction: 'send' | 'receive' | 'both' = 'both',
|
|
266
|
+
): Channel<T> | ChannelRef<T> => {
|
|
267
|
+
const channel = new BufferedChannel<T>(bufferSize, zeroValue)
|
|
268
|
+
|
|
269
|
+
// Wrap the channel with the appropriate ChannelRef based on direction
|
|
270
|
+
if (direction === 'send') {
|
|
271
|
+
return new SendOnlyChannelRef<T>(channel) as ChannelRef<T>
|
|
272
|
+
} else if (direction === 'receive') {
|
|
273
|
+
return new ReceiveOnlyChannelRef<T>(channel) as ChannelRef<T>
|
|
274
|
+
} else {
|
|
275
|
+
return channel
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// A simple implementation of buffered channels
|
|
280
|
+
class BufferedChannel<T> implements Channel<T> {
|
|
281
|
+
private buffer: T[] = []
|
|
282
|
+
private closed: boolean = false
|
|
283
|
+
private capacity: number
|
|
284
|
+
public zeroValue: T // Made public for access by ChannelRef or for type inference
|
|
285
|
+
|
|
286
|
+
// Senders queue: stores { value, resolve for send, reject for send }
|
|
287
|
+
private senders: Array<{
|
|
288
|
+
value: T
|
|
289
|
+
resolveSend: () => void
|
|
290
|
+
rejectSend: (e: Error) => void
|
|
291
|
+
}> = []
|
|
292
|
+
|
|
293
|
+
// Receivers queue for receive(): stores { resolve for receive, reject for receive }
|
|
294
|
+
private receivers: Array<{
|
|
295
|
+
resolveReceive: (value: T) => void
|
|
296
|
+
rejectReceive: (e: Error) => void
|
|
297
|
+
}> = []
|
|
298
|
+
|
|
299
|
+
// Receivers queue for receiveWithOk(): stores { resolve for receiveWithOk }
|
|
300
|
+
private receiversWithOk: Array<{
|
|
301
|
+
resolveReceive: (result: ChannelReceiveResult<T>) => void
|
|
302
|
+
}> = []
|
|
303
|
+
|
|
304
|
+
constructor(capacity: number, zeroValue: T) {
|
|
305
|
+
if (capacity < 0) {
|
|
306
|
+
throw new Error('Channel capacity cannot be negative')
|
|
307
|
+
}
|
|
308
|
+
this.capacity = capacity
|
|
309
|
+
this.zeroValue = zeroValue
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async send(value: T): Promise<void> {
|
|
313
|
+
if (this.closed) {
|
|
314
|
+
throw new Error('send on closed channel')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Attempt to hand off to a waiting receiver (rendezvous)
|
|
318
|
+
if (this.receivers.length > 0) {
|
|
319
|
+
const receiverTask = this.receivers.shift()!
|
|
320
|
+
queueMicrotask(() => receiverTask.resolveReceive(value))
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
if (this.receiversWithOk.length > 0) {
|
|
324
|
+
const receiverTask = this.receiversWithOk.shift()!
|
|
325
|
+
queueMicrotask(() => receiverTask.resolveReceive({ value, ok: true }))
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If no waiting receivers, try to buffer if space is available
|
|
330
|
+
if (this.buffer.length < this.capacity) {
|
|
331
|
+
this.buffer.push(value)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Buffer is full (or capacity is 0 and no receivers are waiting). Sender must block.
|
|
336
|
+
return new Promise<void>((resolve, reject) => {
|
|
337
|
+
this.senders.push({ value, resolveSend: resolve, rejectSend: reject })
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async receive(): Promise<T> {
|
|
342
|
+
// Attempt to get from buffer first
|
|
343
|
+
if (this.buffer.length > 0) {
|
|
344
|
+
const value = this.buffer.shift()!
|
|
345
|
+
// If a sender was waiting because the buffer was full, unblock it.
|
|
346
|
+
if (this.senders.length > 0) {
|
|
347
|
+
const senderTask = this.senders.shift()!
|
|
348
|
+
this.buffer.push(senderTask.value) // Sender's value now goes into buffer
|
|
349
|
+
queueMicrotask(() => senderTask.resolveSend()) // Unblock sender
|
|
350
|
+
}
|
|
351
|
+
return value
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Buffer is empty.
|
|
355
|
+
// If channel is closed (and buffer is empty), return zero value.
|
|
356
|
+
if (this.closed) {
|
|
357
|
+
return this.zeroValue
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Buffer is empty, channel is open.
|
|
361
|
+
// Attempt to rendezvous with a waiting sender.
|
|
362
|
+
if (this.senders.length > 0) {
|
|
363
|
+
const senderTask = this.senders.shift()!
|
|
364
|
+
queueMicrotask(() => senderTask.resolveSend()) // Unblock the sender
|
|
365
|
+
return senderTask.value // Return the value from sender
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Buffer is empty, channel is open, no waiting senders. Receiver must block.
|
|
369
|
+
return new Promise<T>((resolve, reject) => {
|
|
370
|
+
this.receivers.push({ resolveReceive: resolve, rejectReceive: reject })
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
375
|
+
// Attempt to get from buffer first
|
|
376
|
+
if (this.buffer.length > 0) {
|
|
377
|
+
const value = this.buffer.shift()!
|
|
378
|
+
if (this.senders.length > 0) {
|
|
379
|
+
const senderTask = this.senders.shift()!
|
|
380
|
+
this.buffer.push(senderTask.value)
|
|
381
|
+
queueMicrotask(() => senderTask.resolveSend())
|
|
382
|
+
}
|
|
383
|
+
return { value, ok: true }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Buffer is empty.
|
|
387
|
+
// Attempt to rendezvous with a waiting sender.
|
|
388
|
+
if (this.senders.length > 0) {
|
|
389
|
+
const senderTask = this.senders.shift()!
|
|
390
|
+
queueMicrotask(() => senderTask.resolveSend())
|
|
391
|
+
return { value: senderTask.value, ok: true }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Buffer is empty, no waiting senders.
|
|
395
|
+
// If channel is closed, return zero value with ok: false.
|
|
396
|
+
if (this.closed) {
|
|
397
|
+
return { value: this.zeroValue, ok: false }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Buffer is empty, channel is open, no waiting senders. Receiver must block.
|
|
401
|
+
return new Promise<ChannelReceiveResult<T>>((resolve) => {
|
|
402
|
+
this.receiversWithOk.push({ resolveReceive: resolve })
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async selectReceive(id: number): Promise<SelectResult<T>> {
|
|
407
|
+
if (this.buffer.length > 0) {
|
|
408
|
+
const value = this.buffer.shift()!
|
|
409
|
+
if (this.senders.length > 0) {
|
|
410
|
+
const senderTask = this.senders.shift()!
|
|
411
|
+
this.buffer.push(senderTask.value)
|
|
412
|
+
queueMicrotask(() => senderTask.resolveSend())
|
|
413
|
+
}
|
|
414
|
+
return { value, ok: true, id }
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (this.senders.length > 0) {
|
|
418
|
+
const senderTask = this.senders.shift()!
|
|
419
|
+
queueMicrotask(() => senderTask.resolveSend())
|
|
420
|
+
return { value: senderTask.value, ok: true, id }
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (this.closed) {
|
|
424
|
+
return { value: this.zeroValue, ok: false, id }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return new Promise<SelectResult<T>>((resolve) => {
|
|
428
|
+
this.receiversWithOk.push({
|
|
429
|
+
resolveReceive: (result: ChannelReceiveResult<T>) => {
|
|
430
|
+
resolve({ ...result, id })
|
|
431
|
+
},
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
437
|
+
if (this.closed) {
|
|
438
|
+
// A select case sending on a closed channel panics in Go.
|
|
439
|
+
// This will cause Promise.race in selectStatement to reject.
|
|
440
|
+
throw new Error('send on closed channel')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (this.receivers.length > 0) {
|
|
444
|
+
const receiverTask = this.receivers.shift()!
|
|
445
|
+
queueMicrotask(() => receiverTask.resolveReceive(value))
|
|
446
|
+
return { value: true, ok: true, id }
|
|
447
|
+
}
|
|
448
|
+
if (this.receiversWithOk.length > 0) {
|
|
449
|
+
const receiverTask = this.receiversWithOk.shift()!
|
|
450
|
+
queueMicrotask(() => receiverTask.resolveReceive({ value, ok: true }))
|
|
451
|
+
return { value: true, ok: true, id }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (this.buffer.length < this.capacity) {
|
|
455
|
+
this.buffer.push(value)
|
|
456
|
+
return { value: true, ok: true, id }
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return new Promise<SelectResult<boolean>>((resolve, reject) => {
|
|
460
|
+
this.senders.push({
|
|
461
|
+
value,
|
|
462
|
+
resolveSend: () => resolve({ value: true, ok: true, id }),
|
|
463
|
+
rejectSend: (e) => reject(e), // Propagate error if channel closes
|
|
464
|
+
})
|
|
465
|
+
})
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
close(): void {
|
|
469
|
+
if (this.closed) {
|
|
470
|
+
throw new Error('close of closed channel')
|
|
471
|
+
}
|
|
472
|
+
this.closed = true
|
|
473
|
+
|
|
474
|
+
const sendersToNotify = [...this.senders] // Shallow copy for iteration
|
|
475
|
+
this.senders = []
|
|
476
|
+
for (const senderTask of sendersToNotify) {
|
|
477
|
+
queueMicrotask(() =>
|
|
478
|
+
senderTask.rejectSend(new Error('send on closed channel')),
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const receiversToNotify = [...this.receivers]
|
|
483
|
+
this.receivers = []
|
|
484
|
+
for (const receiverTask of receiversToNotify) {
|
|
485
|
+
queueMicrotask(() => receiverTask.resolveReceive(this.zeroValue))
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const receiversWithOkToNotify = [...this.receiversWithOk]
|
|
489
|
+
this.receiversWithOk = []
|
|
490
|
+
for (const receiverTask of receiversWithOkToNotify) {
|
|
491
|
+
queueMicrotask(() =>
|
|
492
|
+
receiverTask.resolveReceive({ value: this.zeroValue, ok: false }),
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
canReceiveNonBlocking(): boolean {
|
|
498
|
+
return this.buffer.length > 0 || this.senders.length > 0 || this.closed
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
canSendNonBlocking(): boolean {
|
|
502
|
+
if (this.closed) {
|
|
503
|
+
return true // Ready to panic
|
|
504
|
+
}
|
|
505
|
+
return (
|
|
506
|
+
this.buffer.length < this.capacity ||
|
|
507
|
+
this.receivers.length > 0 ||
|
|
508
|
+
this.receiversWithOk.length > 0
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Represents a reference to a channel with a specific direction.
|
|
515
|
+
*/
|
|
516
|
+
export interface ChannelRef<T> {
|
|
517
|
+
/**
|
|
518
|
+
* The underlying channel
|
|
519
|
+
*/
|
|
520
|
+
channel: Channel<T>
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* The direction of this channel reference
|
|
524
|
+
*/
|
|
525
|
+
direction: 'send' | 'receive' | 'both'
|
|
526
|
+
|
|
527
|
+
// Channel methods
|
|
528
|
+
send(value: T): Promise<void>
|
|
529
|
+
receive(): Promise<T>
|
|
530
|
+
receiveWithOk(): Promise<ChannelReceiveResult<T>>
|
|
531
|
+
close(): void
|
|
532
|
+
canSendNonBlocking(): boolean
|
|
533
|
+
canReceiveNonBlocking(): boolean
|
|
534
|
+
selectSend(value: T, id: number): Promise<SelectResult<boolean>>
|
|
535
|
+
selectReceive(id: number): Promise<SelectResult<T>>
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* A bidirectional channel reference.
|
|
540
|
+
*/
|
|
541
|
+
export class BidirectionalChannelRef<T> implements ChannelRef<T> {
|
|
542
|
+
direction: 'both' = 'both'
|
|
543
|
+
|
|
544
|
+
constructor(public channel: Channel<T>) {}
|
|
545
|
+
|
|
546
|
+
// Delegate all methods to the underlying channel
|
|
547
|
+
send(value: T): Promise<void> {
|
|
548
|
+
return this.channel.send(value)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
receive(): Promise<T> {
|
|
552
|
+
return this.channel.receive()
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
556
|
+
return this.channel.receiveWithOk()
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
close(): void {
|
|
560
|
+
this.channel.close()
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
canSendNonBlocking(): boolean {
|
|
564
|
+
return this.channel.canSendNonBlocking()
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
canReceiveNonBlocking(): boolean {
|
|
568
|
+
return this.channel.canReceiveNonBlocking()
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
572
|
+
return this.channel.selectSend(value, id)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
selectReceive(id: number): Promise<SelectResult<T>> {
|
|
576
|
+
return this.channel.selectReceive(id)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* A send-only channel reference.
|
|
582
|
+
*/
|
|
583
|
+
export class SendOnlyChannelRef<T> implements ChannelRef<T> {
|
|
584
|
+
direction: 'send' = 'send'
|
|
585
|
+
|
|
586
|
+
constructor(public channel: Channel<T>) {}
|
|
587
|
+
|
|
588
|
+
// Allow send operations
|
|
589
|
+
send(value: T): Promise<void> {
|
|
590
|
+
return this.channel.send(value)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Allow close operations
|
|
594
|
+
close(): void {
|
|
595
|
+
this.channel.close()
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
canSendNonBlocking(): boolean {
|
|
599
|
+
return this.channel.canSendNonBlocking()
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
603
|
+
return this.channel.selectSend(value, id)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Disallow receive operations
|
|
607
|
+
receive(): Promise<T> {
|
|
608
|
+
throw new Error('Cannot receive from send-only channel')
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
612
|
+
throw new Error('Cannot receive from send-only channel')
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
canReceiveNonBlocking(): boolean {
|
|
616
|
+
return false
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
selectReceive(id: number): Promise<SelectResult<T>> {
|
|
620
|
+
throw new Error('Cannot receive from send-only channel')
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* A receive-only channel reference.
|
|
626
|
+
*/
|
|
627
|
+
export class ReceiveOnlyChannelRef<T> implements ChannelRef<T> {
|
|
628
|
+
direction: 'receive' = 'receive'
|
|
629
|
+
|
|
630
|
+
constructor(public channel: Channel<T>) {}
|
|
631
|
+
|
|
632
|
+
// Allow receive operations
|
|
633
|
+
receive(): Promise<T> {
|
|
634
|
+
return this.channel.receive()
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
receiveWithOk(): Promise<ChannelReceiveResult<T>> {
|
|
638
|
+
return this.channel.receiveWithOk()
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
canReceiveNonBlocking(): boolean {
|
|
642
|
+
return this.channel.canReceiveNonBlocking()
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
selectReceive(id: number): Promise<SelectResult<T>> {
|
|
646
|
+
return this.channel.selectReceive(id)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Disallow send operations
|
|
650
|
+
send(value: T): Promise<void> {
|
|
651
|
+
throw new Error('Cannot send to receive-only channel')
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Disallow close operations
|
|
655
|
+
close(): void {
|
|
656
|
+
throw new Error('Cannot close receive-only channel')
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
canSendNonBlocking(): boolean {
|
|
660
|
+
return false
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
|
|
664
|
+
throw new Error('Cannot send to receive-only channel')
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Creates a new channel reference with the specified direction.
|
|
670
|
+
*/
|
|
671
|
+
export function makeChannelRef<T>(
|
|
672
|
+
channel: Channel<T>,
|
|
673
|
+
direction: 'send' | 'receive' | 'both',
|
|
674
|
+
): ChannelRef<T> {
|
|
675
|
+
switch (direction) {
|
|
676
|
+
case 'send':
|
|
677
|
+
return new SendOnlyChannelRef<T>(channel)
|
|
678
|
+
case 'receive':
|
|
679
|
+
return new ReceiveOnlyChannelRef<T>(channel)
|
|
680
|
+
default: // 'both'
|
|
681
|
+
return new BidirectionalChannelRef<T>(channel)
|
|
682
|
+
}
|
|
683
|
+
}
|