goscript 0.0.25 → 0.0.28
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 +259 -55
- package/compiler/assignment.go +2 -2
- package/compiler/builtin_test.go +1 -1
- package/compiler/compiler.go +201 -49
- package/compiler/compiler_test.go +53 -0
- package/compiler/composite-lit.go +32 -8
- package/compiler/decl.go +6 -6
- package/compiler/expr-call.go +83 -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 +11 -3
- package/compiler/spec.go +18 -1
- package/compiler/stmt-assign.go +35 -0
- package/compiler/type-assert.go +87 -0
- package/compiler/type.go +5 -2
- package/dist/gs/builtin/builtin.d.ts +19 -1
- package/dist/gs/builtin/builtin.js +85 -5
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js +59 -26
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/cmp/index.js.map +1 -1
- package/dist/gs/context/context.d.ts +1 -1
- package/dist/gs/context/context.js +20 -11
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/errors/errors.d.ts +7 -0
- package/dist/gs/errors/errors.js +190 -0
- package/dist/gs/errors/errors.js.map +1 -0
- package/dist/gs/errors/index.d.ts +1 -0
- package/dist/gs/errors/index.js +2 -0
- package/dist/gs/errors/index.js.map +1 -0
- package/dist/gs/internal/goarch/index.js +1 -1
- package/dist/gs/internal/goarch/index.js.map +1 -1
- 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/iter/iter.js.map +1 -1
- package/dist/gs/math/bits/index.js +34 -32
- package/dist/gs/math/bits/index.js.map +1 -1
- package/dist/gs/runtime/runtime.d.ts +1 -0
- package/dist/gs/runtime/runtime.js +15 -18
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +1 -1
- package/dist/gs/slices/slices.js +1 -1
- package/dist/gs/slices/slices.js.map +1 -1
- 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.js +7 -7
- 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 +158 -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/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/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 +243 -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.md +116 -0
- package/gs/time/godoc.txt +116 -0
- package/gs/time/index.ts +1 -0
- package/gs/time/time.ts +272 -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 +6 -4
|
@@ -0,0 +1,1030 @@
|
|
|
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
|
+
* Slice<number> can be represented as Uint8Array.
|
|
23
|
+
*/
|
|
24
|
+
export type Slice<T> =
|
|
25
|
+
| T[]
|
|
26
|
+
| SliceProxy<T>
|
|
27
|
+
| null
|
|
28
|
+
| (T extends number ? Uint8Array : never)
|
|
29
|
+
|
|
30
|
+
// asArray converts a slice to a JavaScript array.
|
|
31
|
+
export function asArray<T>(slice: Slice<T>): T[] {
|
|
32
|
+
if (slice === null || slice === undefined) {
|
|
33
|
+
return []
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (slice instanceof Uint8Array) {
|
|
37
|
+
return Array.from(slice) as T[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (isComplexSlice(slice)) {
|
|
41
|
+
const result: T[] = []
|
|
42
|
+
for (let i = 0; i < slice.__meta__.length; i++) {
|
|
43
|
+
result.push(slice.__meta__.backing[slice.__meta__.offset + i])
|
|
44
|
+
}
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(slice)) {
|
|
49
|
+
return slice
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* isComplexSlice checks if a slice is a complex slice (has __meta__ property)
|
|
57
|
+
*/
|
|
58
|
+
function isComplexSlice<T>(slice: Slice<T>): slice is SliceProxy<T> {
|
|
59
|
+
return (
|
|
60
|
+
slice !== null &&
|
|
61
|
+
slice !== undefined &&
|
|
62
|
+
typeof slice === 'object' &&
|
|
63
|
+
'__meta__' in slice &&
|
|
64
|
+
slice.__meta__ !== undefined
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Creates a new slice with the specified length and capacity.
|
|
70
|
+
* @param length The length of the slice.
|
|
71
|
+
* @param capacity The capacity of the slice (optional).
|
|
72
|
+
* @returns A new slice.
|
|
73
|
+
*/
|
|
74
|
+
export const makeSlice = <T>(
|
|
75
|
+
length: number,
|
|
76
|
+
capacity?: number,
|
|
77
|
+
typeHint?: string,
|
|
78
|
+
): Slice<T> => {
|
|
79
|
+
if (typeHint === 'byte') {
|
|
80
|
+
// Uint8Array is initialized to zeros by default.
|
|
81
|
+
// Capacity for Uint8Array is its length.
|
|
82
|
+
return new Uint8Array(length) as Slice<T>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const actualCapacity = capacity === undefined ? length : capacity
|
|
86
|
+
if (length < 0 || actualCapacity < 0 || length > actualCapacity) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Invalid slice length (${length}) or capacity (${actualCapacity})`,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let zeroVal: any
|
|
93
|
+
switch (typeHint) {
|
|
94
|
+
case 'number':
|
|
95
|
+
zeroVal = 0
|
|
96
|
+
break
|
|
97
|
+
case 'boolean':
|
|
98
|
+
zeroVal = false
|
|
99
|
+
break
|
|
100
|
+
case 'string':
|
|
101
|
+
zeroVal = ''
|
|
102
|
+
break
|
|
103
|
+
default:
|
|
104
|
+
zeroVal = null // Default for objects, complex types, or unspecified
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const backingArr = new Array<T>(actualCapacity)
|
|
108
|
+
// Initialize the relevant part of the backing array
|
|
109
|
+
for (let i = 0; i < length; i++) {
|
|
110
|
+
backingArr[i] = zeroVal
|
|
111
|
+
}
|
|
112
|
+
// The rest of backingArr (from length to actualCapacity-1) remains uninitialized (undefined),
|
|
113
|
+
// representing available capacity.
|
|
114
|
+
|
|
115
|
+
// The proxyTargetArray serves as the shell for the proxy.
|
|
116
|
+
// Its elements up to 'length' should reflect the initialized part of the slice.
|
|
117
|
+
const proxyTargetArray = new Array<T>(length)
|
|
118
|
+
for (let i = 0; i < length; i++) {
|
|
119
|
+
proxyTargetArray[i] = backingArr[i] // Or simply zeroVal
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const proxy = proxyTargetArray as SliceProxy<T>
|
|
123
|
+
proxy.__meta__ = {
|
|
124
|
+
backing: backingArr,
|
|
125
|
+
offset: 0,
|
|
126
|
+
length: length,
|
|
127
|
+
capacity: actualCapacity,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return proxy
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* goSlice creates a slice from s[low:high:max]
|
|
135
|
+
* Arguments mirror Go semantics; omitted indices are undefined.
|
|
136
|
+
*
|
|
137
|
+
* @param s The original slice
|
|
138
|
+
* @param low Starting index (defaults to 0)
|
|
139
|
+
* @param high Ending index (defaults to s.length)
|
|
140
|
+
* @param max Capacity limit (defaults to original capacity)
|
|
141
|
+
*/
|
|
142
|
+
export const goSlice = <T>( // T can be number for Uint8Array case
|
|
143
|
+
s: Slice<T>,
|
|
144
|
+
low?: number,
|
|
145
|
+
high?: number,
|
|
146
|
+
max?: number,
|
|
147
|
+
): Slice<T> => {
|
|
148
|
+
const handler = {
|
|
149
|
+
get(target: any, prop: string | symbol): any {
|
|
150
|
+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
151
|
+
const index = Number(prop)
|
|
152
|
+
if (index >= 0 && index < target.__meta__.length) {
|
|
153
|
+
return target.__meta__.backing[target.__meta__.offset + index]
|
|
154
|
+
}
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Slice index out of range: ${index} >= ${target.__meta__.length}`,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (prop === 'length') {
|
|
161
|
+
return target.__meta__.length
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (prop === '__meta__') {
|
|
165
|
+
return target.__meta__
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
prop === 'slice' ||
|
|
170
|
+
prop === 'map' ||
|
|
171
|
+
prop === 'filter' ||
|
|
172
|
+
prop === 'reduce' ||
|
|
173
|
+
prop === 'forEach' ||
|
|
174
|
+
prop === Symbol.iterator
|
|
175
|
+
) {
|
|
176
|
+
const backingSlice = target.__meta__.backing.slice(
|
|
177
|
+
target.__meta__.offset,
|
|
178
|
+
target.__meta__.offset + target.__meta__.length,
|
|
179
|
+
)
|
|
180
|
+
return backingSlice[prop].bind(backingSlice)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return Reflect.get(target, prop)
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
set(target: any, prop: string | symbol, value: any): boolean {
|
|
187
|
+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
188
|
+
const index = Number(prop)
|
|
189
|
+
if (index >= 0 && index < target.__meta__.length) {
|
|
190
|
+
target.__meta__.backing[target.__meta__.offset + index] = value
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
index === target.__meta__.length &&
|
|
195
|
+
target.__meta__.length < target.__meta__.capacity
|
|
196
|
+
) {
|
|
197
|
+
target.__meta__.backing[target.__meta__.offset + index] = value
|
|
198
|
+
target.__meta__.length++
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Slice index out of range: ${index} >= ${target.__meta__.length}`,
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (prop === 'length' || prop === '__meta__') {
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return Reflect.set(target, prop, value)
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (s instanceof Uint8Array) {
|
|
215
|
+
const actualLow = low ?? 0
|
|
216
|
+
const actualHigh = high ?? s.length
|
|
217
|
+
|
|
218
|
+
if (actualLow < 0 || actualHigh < actualLow || actualHigh > s.length) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Invalid slice indices: low ${actualLow}, high ${actualHigh} for Uint8Array of length ${s.length}`,
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const subArrayView = s.subarray(actualLow, actualHigh) // This is Uint8Array
|
|
225
|
+
|
|
226
|
+
if (max !== undefined) {
|
|
227
|
+
if (max < actualHigh || max > s.length) {
|
|
228
|
+
// max is relative to the original s.length (capacity)
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Invalid max index: ${max}. Constraints: low ${actualLow} <= high ${actualHigh} <= max <= original_length ${s.length}`,
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const newLength = subArrayView.length // actualHigh - actualLow
|
|
235
|
+
const newCap = max - actualLow // Capacity of the new slice view
|
|
236
|
+
|
|
237
|
+
if (newCap !== newLength) {
|
|
238
|
+
// Capacity is different from length, so return SliceProxy<number>
|
|
239
|
+
// The original s was Uint8Array, so T is effectively 'number' for this path.
|
|
240
|
+
const backingNumbers = Array.from(subArrayView) // Convert Uint8Array data to number[]
|
|
241
|
+
|
|
242
|
+
const proxyTarget = {
|
|
243
|
+
__meta__: {
|
|
244
|
+
backing: backingNumbers, // number[]
|
|
245
|
+
offset: 0, // Offset is 0 because backingNumbers is a direct copy
|
|
246
|
+
length: newLength,
|
|
247
|
+
capacity: newCap,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
// Explicitly cast to Slice<T> after ensuring T is number for this branch.
|
|
251
|
+
return new Proxy(
|
|
252
|
+
proxyTarget,
|
|
253
|
+
handler,
|
|
254
|
+
) as unknown as SliceProxy<number> as Slice<T>
|
|
255
|
+
} else {
|
|
256
|
+
// newCap === newLength, standard Uint8Array is fine.
|
|
257
|
+
return subArrayView as Slice<T> // T is number
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
// max is not defined, return the Uint8Array subarray view directly.
|
|
261
|
+
return subArrayView as Slice<T> // T is number
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (s === null || s === undefined) {
|
|
266
|
+
throw new Error('Cannot slice nil')
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const slen = len(s)
|
|
270
|
+
low = low ?? 0
|
|
271
|
+
high = high ?? slen
|
|
272
|
+
|
|
273
|
+
if (low < 0 || high < low) {
|
|
274
|
+
throw new Error(`Invalid slice indices: ${low}:${high}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// In Go, high can be up to capacity, not just length
|
|
278
|
+
const scap = cap(s)
|
|
279
|
+
if (high > scap) {
|
|
280
|
+
throw new Error(`Slice index out of range: ${high} > ${scap}`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
Array.isArray(s) &&
|
|
285
|
+
!isComplexSlice(s) &&
|
|
286
|
+
low === 0 &&
|
|
287
|
+
high === s.length &&
|
|
288
|
+
max === undefined
|
|
289
|
+
) {
|
|
290
|
+
return s
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let backing: T[]
|
|
294
|
+
let oldOffset = 0
|
|
295
|
+
let oldCap = scap
|
|
296
|
+
|
|
297
|
+
// Get the backing array and offset
|
|
298
|
+
if (isComplexSlice(s)) {
|
|
299
|
+
backing = s.__meta__.backing
|
|
300
|
+
oldOffset = s.__meta__.offset
|
|
301
|
+
oldCap = s.__meta__.capacity
|
|
302
|
+
} else {
|
|
303
|
+
backing = s as T[]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let newCap
|
|
307
|
+
if (max !== undefined) {
|
|
308
|
+
if (max < high) {
|
|
309
|
+
throw new Error(`Invalid slice indices: ${low}:${high}:${max}`)
|
|
310
|
+
}
|
|
311
|
+
if (isComplexSlice(s) && max > oldOffset + oldCap) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`Slice index out of range: ${max} > ${oldOffset + oldCap}`,
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
if (!isComplexSlice(s) && max > s.length) {
|
|
317
|
+
throw new Error(`Slice index out of range: ${max} > ${s.length}`)
|
|
318
|
+
}
|
|
319
|
+
newCap = max - low
|
|
320
|
+
} else {
|
|
321
|
+
// For slices of slices, capacity should be the capacity of the original slice minus the low index
|
|
322
|
+
if (isComplexSlice(s)) {
|
|
323
|
+
newCap = oldCap - low
|
|
324
|
+
} else {
|
|
325
|
+
newCap = s.length - low
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const newLength = high - low
|
|
330
|
+
const newOffset = oldOffset + low
|
|
331
|
+
|
|
332
|
+
const target = {
|
|
333
|
+
__meta__: {
|
|
334
|
+
backing: backing,
|
|
335
|
+
offset: newOffset,
|
|
336
|
+
length: newLength,
|
|
337
|
+
capacity: newCap,
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// const handler = { ... } // Handler is now defined at the top
|
|
342
|
+
|
|
343
|
+
return new Proxy(target, handler) as unknown as SliceProxy<T>
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Converts a JavaScript array to a Go slice.
|
|
348
|
+
* For multi-dimensional arrays, recursively converts nested arrays to slices.
|
|
349
|
+
* @param arr The JavaScript array to convert
|
|
350
|
+
* @param depth How many levels of nesting to convert (default: 1, use Infinity for all levels)
|
|
351
|
+
* @returns A Go slice containing the same elements
|
|
352
|
+
*/
|
|
353
|
+
export const arrayToSlice = <T>(
|
|
354
|
+
arr: T[] | null | undefined,
|
|
355
|
+
depth: number = 1,
|
|
356
|
+
): Slice<T> => {
|
|
357
|
+
if (arr == null) return [] as T[]
|
|
358
|
+
|
|
359
|
+
if (arr.length === 0) return arr
|
|
360
|
+
|
|
361
|
+
const target = {
|
|
362
|
+
__meta__: {
|
|
363
|
+
backing: arr,
|
|
364
|
+
offset: 0,
|
|
365
|
+
length: arr.length,
|
|
366
|
+
capacity: arr.length,
|
|
367
|
+
},
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const handler = {
|
|
371
|
+
get(target: any, prop: string | symbol): any {
|
|
372
|
+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
373
|
+
const index = Number(prop)
|
|
374
|
+
if (index >= 0 && index < target.__meta__.length) {
|
|
375
|
+
return target.__meta__.backing[target.__meta__.offset + index]
|
|
376
|
+
}
|
|
377
|
+
throw new Error(
|
|
378
|
+
`Slice index out of range: ${index} >= ${target.__meta__.length}`,
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (prop === 'length') {
|
|
383
|
+
return target.__meta__.length
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (prop === '__meta__') {
|
|
387
|
+
return target.__meta__
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
prop === 'slice' ||
|
|
392
|
+
prop === 'map' ||
|
|
393
|
+
prop === 'filter' ||
|
|
394
|
+
prop === 'reduce' ||
|
|
395
|
+
prop === 'forEach' ||
|
|
396
|
+
prop === Symbol.iterator
|
|
397
|
+
) {
|
|
398
|
+
const backingSlice = target.__meta__.backing.slice(
|
|
399
|
+
target.__meta__.offset,
|
|
400
|
+
target.__meta__.offset + target.__meta__.length,
|
|
401
|
+
)
|
|
402
|
+
return backingSlice[prop].bind(backingSlice)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return Reflect.get(target, prop)
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
set(target: any, prop: string | symbol, value: any): boolean {
|
|
409
|
+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
|
|
410
|
+
const index = Number(prop)
|
|
411
|
+
if (index >= 0 && index < target.__meta__.length) {
|
|
412
|
+
target.__meta__.backing[target.__meta__.offset + index] = value
|
|
413
|
+
return true
|
|
414
|
+
}
|
|
415
|
+
if (
|
|
416
|
+
index === target.__meta__.length &&
|
|
417
|
+
target.__meta__.length < target.__meta__.capacity
|
|
418
|
+
) {
|
|
419
|
+
target.__meta__.backing[target.__meta__.offset + index] = value
|
|
420
|
+
target.__meta__.length++
|
|
421
|
+
return true
|
|
422
|
+
}
|
|
423
|
+
throw new Error(
|
|
424
|
+
`Slice index out of range: ${index} >= ${target.__meta__.length}`,
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (prop === 'length' || prop === '__meta__') {
|
|
429
|
+
return false
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return Reflect.set(target, prop, value)
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Recursively convert nested arrays if depth > 1
|
|
437
|
+
if (depth > 1 && arr.length > 0) {
|
|
438
|
+
for (let i = 0; i < arr.length; i++) {
|
|
439
|
+
const item = arr[i]
|
|
440
|
+
if (isComplexSlice(item as any)) {
|
|
441
|
+
} else if (Array.isArray(item)) {
|
|
442
|
+
arr[i] = arrayToSlice(item as any[], depth - 1) as any
|
|
443
|
+
} else if (
|
|
444
|
+
item &&
|
|
445
|
+
typeof item === 'object' &&
|
|
446
|
+
isComplexSlice(item as any)
|
|
447
|
+
) {
|
|
448
|
+
// Preserve capacity information for complex slices
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return new Proxy(target, handler) as unknown as SliceProxy<T>
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Returns the length of a collection (string, array, slice, map, or set).
|
|
458
|
+
* @param obj The collection to get the length of.
|
|
459
|
+
* @returns The length of the collection.
|
|
460
|
+
*/
|
|
461
|
+
export const len = <T = unknown, V = unknown>(
|
|
462
|
+
obj:
|
|
463
|
+
| string
|
|
464
|
+
| Array<T>
|
|
465
|
+
| Slice<T>
|
|
466
|
+
| Map<T, V>
|
|
467
|
+
| Set<T>
|
|
468
|
+
| Uint8Array
|
|
469
|
+
| null
|
|
470
|
+
| undefined,
|
|
471
|
+
): number => {
|
|
472
|
+
if (obj === null || obj === undefined) {
|
|
473
|
+
return 0
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (typeof obj === 'string') {
|
|
477
|
+
return stringLen(obj) // Call new stringLen for strings
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (obj instanceof Map || obj instanceof Set) {
|
|
481
|
+
return obj.size
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (obj instanceof Uint8Array) {
|
|
485
|
+
return obj.length
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (isComplexSlice(obj)) {
|
|
489
|
+
return obj.__meta__.length
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (Array.isArray(obj)) {
|
|
493
|
+
return obj.length
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
throw new Error('cannot determine len of this type')
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Returns the capacity of a slice.
|
|
501
|
+
* @param obj The slice.
|
|
502
|
+
* @returns The capacity of the slice.
|
|
503
|
+
*/
|
|
504
|
+
export const cap = <T>(obj: Slice<T> | Uint8Array): number => {
|
|
505
|
+
if (obj === null || obj === undefined) {
|
|
506
|
+
return 0
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (obj instanceof Uint8Array) {
|
|
510
|
+
return obj.length // Uint8Array capacity is its length
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (isComplexSlice(obj)) {
|
|
514
|
+
return obj.__meta__.capacity
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (Array.isArray(obj)) {
|
|
518
|
+
return obj.length
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return 0
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Appends elements to a slice.
|
|
526
|
+
* Note: In Go, append can return a new slice if the underlying array is reallocated.
|
|
527
|
+
* This helper emulates that by returning the modified or new slice.
|
|
528
|
+
* @param slice The slice to append to.
|
|
529
|
+
* @param elements The elements to append.
|
|
530
|
+
* @returns The modified or new slice.
|
|
531
|
+
*/
|
|
532
|
+
export const append = <T>(
|
|
533
|
+
slice: Slice<T> | Uint8Array,
|
|
534
|
+
...elements: any[]
|
|
535
|
+
): Slice<T> => {
|
|
536
|
+
// 1. Flatten all elements from the varargs `...elements` into `varargsElements`.
|
|
537
|
+
// Determine if the result should be a Uint8Array.
|
|
538
|
+
const inputIsUint8Array = slice instanceof Uint8Array
|
|
539
|
+
const appendingUint8Array = elements.some((el) => el instanceof Uint8Array)
|
|
540
|
+
const produceUint8Array =
|
|
541
|
+
inputIsUint8Array ||
|
|
542
|
+
appendingUint8Array ||
|
|
543
|
+
(slice === null && appendingUint8Array)
|
|
544
|
+
|
|
545
|
+
// If producing Uint8Array, all elements must be numbers and potentially flattened from other Uint8Arrays/number slices.
|
|
546
|
+
if (produceUint8Array) {
|
|
547
|
+
let combinedBytes: number[] = []
|
|
548
|
+
// Add bytes from the original slice if it exists and is numeric.
|
|
549
|
+
if (inputIsUint8Array) {
|
|
550
|
+
combinedBytes.push(...Array.from(slice as Uint8Array))
|
|
551
|
+
} else if (slice !== null && slice !== undefined) {
|
|
552
|
+
// Original was Slice<number> or number[]
|
|
553
|
+
const sliceLen = len(slice)
|
|
554
|
+
for (let i = 0; i < sliceLen; i++) {
|
|
555
|
+
const val = (slice as any)[i]
|
|
556
|
+
if (typeof val !== 'number') {
|
|
557
|
+
throw new Error(
|
|
558
|
+
'Cannot produce Uint8Array: original slice contains non-number elements.',
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
combinedBytes.push(val)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Add bytes from the varargs elements.
|
|
565
|
+
// For Uint8Array, elements are always flattened if they are slices/Uint8Arrays.
|
|
566
|
+
for (const item of elements) {
|
|
567
|
+
if (item instanceof Uint8Array) {
|
|
568
|
+
combinedBytes.push(...Array.from(item))
|
|
569
|
+
} else if (isComplexSlice(item) || Array.isArray(item)) {
|
|
570
|
+
const itemLen = len(item as Slice<any>)
|
|
571
|
+
for (let i = 0; i < itemLen; i++) {
|
|
572
|
+
const val = (item as any)[i]
|
|
573
|
+
if (typeof val !== 'number') {
|
|
574
|
+
throw new Error(
|
|
575
|
+
'Cannot produce Uint8Array: appended elements contain non-numbers.',
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
combinedBytes.push(val)
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
if (typeof item !== 'number') {
|
|
582
|
+
throw new Error(
|
|
583
|
+
'Cannot produce Uint8Array: appended elements contain non-numbers.',
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
combinedBytes.push(item)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const newArr = new Uint8Array(combinedBytes.length)
|
|
590
|
+
newArr.set(combinedBytes)
|
|
591
|
+
return newArr as Slice<T>
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Handle generic Slice<T> (non-Uint8Array result).
|
|
595
|
+
// In this case, `elements` are treated as individual items to append,
|
|
596
|
+
// as the Go transpiler is responsible for spreading (`...`) if needed.
|
|
597
|
+
const numAdded = elements.length
|
|
598
|
+
|
|
599
|
+
if (numAdded === 0) {
|
|
600
|
+
return slice
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
let originalElements: T[] = []
|
|
604
|
+
let oldCapacity: number
|
|
605
|
+
let isOriginalComplex = false
|
|
606
|
+
let originalBacking: T[] | undefined = undefined
|
|
607
|
+
let originalOffset = 0
|
|
608
|
+
|
|
609
|
+
if (slice === null || slice === undefined) {
|
|
610
|
+
oldCapacity = 0
|
|
611
|
+
} else if (isComplexSlice(slice)) {
|
|
612
|
+
const meta = slice.__meta__
|
|
613
|
+
for (let i = 0; i < meta.length; i++)
|
|
614
|
+
originalElements.push(meta.backing[meta.offset + i])
|
|
615
|
+
oldCapacity = meta.capacity
|
|
616
|
+
isOriginalComplex = true
|
|
617
|
+
originalBacking = meta.backing
|
|
618
|
+
originalOffset = meta.offset
|
|
619
|
+
} else {
|
|
620
|
+
// Simple T[] array
|
|
621
|
+
originalElements = (slice as T[]).slice()
|
|
622
|
+
oldCapacity = (slice as T[]).length
|
|
623
|
+
}
|
|
624
|
+
const oldLength = originalElements.length
|
|
625
|
+
const newLength = oldLength + numAdded
|
|
626
|
+
|
|
627
|
+
// Case 1: Modify in-place if original was SliceProxy and has enough capacity.
|
|
628
|
+
if (isOriginalComplex && newLength <= oldCapacity && originalBacking) {
|
|
629
|
+
for (let i = 0; i < numAdded; i++) {
|
|
630
|
+
originalBacking[originalOffset + oldLength + i] = elements[i] as T
|
|
631
|
+
}
|
|
632
|
+
const resultProxy = new Array(newLength) as SliceProxy<T>
|
|
633
|
+
for (let i = 0; i < newLength; i++)
|
|
634
|
+
resultProxy[i] = originalBacking[originalOffset + i]
|
|
635
|
+
resultProxy.__meta__ = {
|
|
636
|
+
backing: originalBacking,
|
|
637
|
+
offset: originalOffset,
|
|
638
|
+
length: newLength,
|
|
639
|
+
capacity: oldCapacity,
|
|
640
|
+
}
|
|
641
|
+
return resultProxy
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Case 2: Reallocation is needed.
|
|
645
|
+
let newCapacity = oldCapacity
|
|
646
|
+
if (newCapacity === 0) {
|
|
647
|
+
newCapacity = newLength
|
|
648
|
+
} else if (oldLength < 1024) {
|
|
649
|
+
newCapacity = Math.max(oldCapacity * 2, newLength)
|
|
650
|
+
} else {
|
|
651
|
+
newCapacity = Math.max(oldCapacity + Math.floor(oldCapacity / 4), newLength)
|
|
652
|
+
}
|
|
653
|
+
if (newCapacity < newLength) {
|
|
654
|
+
newCapacity = newLength
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const newBacking = new Array<T>(newCapacity)
|
|
658
|
+
for (let i = 0; i < oldLength; i++) {
|
|
659
|
+
newBacking[i] = originalElements[i]
|
|
660
|
+
}
|
|
661
|
+
for (let i = 0; i < numAdded; i++) {
|
|
662
|
+
newBacking[oldLength + i] = elements[i] as T
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const resultProxy = new Array(newLength) as SliceProxy<T>
|
|
666
|
+
for (let i = 0; i < newLength; i++) resultProxy[i] = newBacking[i]
|
|
667
|
+
resultProxy.__meta__ = {
|
|
668
|
+
backing: newBacking,
|
|
669
|
+
offset: 0,
|
|
670
|
+
length: newLength,
|
|
671
|
+
capacity: newCapacity,
|
|
672
|
+
}
|
|
673
|
+
return resultProxy
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Copies elements from src to dst.
|
|
678
|
+
* @param dst The destination slice.
|
|
679
|
+
* @param src The source slice.
|
|
680
|
+
* @returns The number of elements copied.
|
|
681
|
+
*/
|
|
682
|
+
export const copy = <T>(dst: Slice<T>, src: Slice<T>): number => {
|
|
683
|
+
if (dst === null || src === null) {
|
|
684
|
+
return 0
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const dstLen = len(dst)
|
|
688
|
+
const srcLen = len(src)
|
|
689
|
+
const count = Math.min(dstLen, srcLen)
|
|
690
|
+
|
|
691
|
+
if (count === 0) {
|
|
692
|
+
return 0
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const isDstUint8Array = dst instanceof Uint8Array
|
|
696
|
+
const isSrcUint8Array = src instanceof Uint8Array
|
|
697
|
+
|
|
698
|
+
if (isDstUint8Array && isSrcUint8Array) {
|
|
699
|
+
;(dst as Uint8Array).set((src as Uint8Array).subarray(0, count))
|
|
700
|
+
} else if (isDstUint8Array) {
|
|
701
|
+
// dst is Uint8Array, src is Slice<number> or number[]
|
|
702
|
+
const dstUint8 = dst as Uint8Array
|
|
703
|
+
if (isComplexSlice(src)) {
|
|
704
|
+
const srcMeta = (src as SliceProxy<number>).__meta__
|
|
705
|
+
for (let i = 0; i < count; i++) {
|
|
706
|
+
dstUint8[i] = srcMeta.backing[srcMeta.offset + i]
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
// src is number[]
|
|
710
|
+
const srcArray = src as number[]
|
|
711
|
+
for (let i = 0; i < count; i++) {
|
|
712
|
+
dstUint8[i] = srcArray[i]
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
} else if (isSrcUint8Array) {
|
|
716
|
+
// src is Uint8Array, dst is Slice<number> or number[]
|
|
717
|
+
const srcUint8 = src as Uint8Array
|
|
718
|
+
if (isComplexSlice(dst)) {
|
|
719
|
+
const dstMeta = (dst as SliceProxy<number>).__meta__
|
|
720
|
+
const dstBacking = dstMeta.backing
|
|
721
|
+
const dstOffset = dstMeta.offset
|
|
722
|
+
for (let i = 0; i < count; i++) {
|
|
723
|
+
dstBacking[dstOffset + i] = srcUint8[i]
|
|
724
|
+
// Also update the proxy view if dst is a proxy
|
|
725
|
+
;(dst as any)[i] = srcUint8[i]
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
// dst is number[]
|
|
729
|
+
const dstArray = dst as number[]
|
|
730
|
+
for (let i = 0; i < count; i++) {
|
|
731
|
+
dstArray[i] = srcUint8[i]
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
// Both are Slice<T> or T[] (original logic)
|
|
736
|
+
if (isComplexSlice(dst)) {
|
|
737
|
+
const dstOffset = (dst as SliceProxy<T>).__meta__.offset
|
|
738
|
+
const dstBacking = (dst as SliceProxy<T>).__meta__.backing
|
|
739
|
+
|
|
740
|
+
if (isComplexSlice(src)) {
|
|
741
|
+
const srcOffset = (src as SliceProxy<T>).__meta__.offset
|
|
742
|
+
const srcBacking = (src as SliceProxy<T>).__meta__.backing
|
|
743
|
+
for (let i = 0; i < count; i++) {
|
|
744
|
+
dstBacking[dstOffset + i] = srcBacking[srcOffset + i]
|
|
745
|
+
;(dst as any)[i] = srcBacking[srcOffset + i] // Update proxy
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
// src is T[]
|
|
749
|
+
const srcArray = src as T[]
|
|
750
|
+
for (let i = 0; i < count; i++) {
|
|
751
|
+
dstBacking[dstOffset + i] = srcArray[i]
|
|
752
|
+
;(dst as any)[i] = srcArray[i] // Update proxy
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
} else {
|
|
756
|
+
// dst is T[]
|
|
757
|
+
const dstArray = dst as T[]
|
|
758
|
+
if (isComplexSlice(src)) {
|
|
759
|
+
const srcOffset = (src as SliceProxy<T>).__meta__.offset
|
|
760
|
+
const srcBacking = (src as SliceProxy<T>).__meta__.backing
|
|
761
|
+
for (let i = 0; i < count; i++) {
|
|
762
|
+
dstArray[i] = srcBacking[srcOffset + i]
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
// src is T[]
|
|
766
|
+
const srcArray = src as T[]
|
|
767
|
+
for (let i = 0; i < count; i++) {
|
|
768
|
+
dstArray[i] = srcArray[i]
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return count
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Accesses an element at a specific index for various Go-like types (string, slice, array).
|
|
778
|
+
* Mimics Go's indexing behavior: `myCollection[index]`
|
|
779
|
+
* For strings, it returns the byte value at the specified byte index.
|
|
780
|
+
* For slices/arrays, it returns the element at the specified index.
|
|
781
|
+
* This is used when dealing with types like "string | []byte"
|
|
782
|
+
* @param collection The string, Slice, or Array to access.
|
|
783
|
+
* @param index The index.
|
|
784
|
+
* @returns The element or byte value at the specified index.
|
|
785
|
+
* @throws Error if index is out of bounds or type is unsupported.
|
|
786
|
+
*/
|
|
787
|
+
export function index<T>(
|
|
788
|
+
collection: string | Slice<T> | T[],
|
|
789
|
+
index: number,
|
|
790
|
+
): T | number {
|
|
791
|
+
if (collection === null || collection === undefined) {
|
|
792
|
+
throw new Error('runtime error: index on nil or undefined collection')
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (typeof collection === 'string') {
|
|
796
|
+
return indexString(collection, index) // Use the existing indexString for byte access
|
|
797
|
+
} else if (collection instanceof Uint8Array) {
|
|
798
|
+
if (index < 0 || index >= collection.length) {
|
|
799
|
+
throw new Error(
|
|
800
|
+
`runtime error: index out of range [${index}] with length ${collection.length}`,
|
|
801
|
+
)
|
|
802
|
+
}
|
|
803
|
+
return collection[index]
|
|
804
|
+
} else if (isComplexSlice(collection)) {
|
|
805
|
+
if (index < 0 || index >= collection.__meta__.length) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`runtime error: index out of range [${index}] with length ${collection.__meta__.length}`,
|
|
808
|
+
)
|
|
809
|
+
}
|
|
810
|
+
return collection.__meta__.backing[collection.__meta__.offset + index]
|
|
811
|
+
} else if (Array.isArray(collection)) {
|
|
812
|
+
if (index < 0 || index >= collection.length) {
|
|
813
|
+
throw new Error(
|
|
814
|
+
`runtime error: index out of range [${index}] with length ${collection.length}`,
|
|
815
|
+
)
|
|
816
|
+
}
|
|
817
|
+
return collection[index]
|
|
818
|
+
}
|
|
819
|
+
throw new Error('runtime error: index on unsupported type')
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Converts a string to an array of Unicode code points (runes).
|
|
824
|
+
* @param str The input string.
|
|
825
|
+
* @returns An array of numbers representing the Unicode code points.
|
|
826
|
+
*/
|
|
827
|
+
export const stringToRunes = (str: string): number[] => {
|
|
828
|
+
return Array.from(str).map((c) => c.codePointAt(0) || 0)
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Converts an array of Unicode code points (runes) to a string.
|
|
833
|
+
* @param runes The input array of numbers representing Unicode code points.
|
|
834
|
+
* @returns The resulting string.
|
|
835
|
+
*/
|
|
836
|
+
export const runesToString = (runes: Slice<number>): string => {
|
|
837
|
+
return runes?.length ? String.fromCharCode(...runes) : ''
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Converts a number to a byte (uint8) by truncating to the range 0-255.
|
|
842
|
+
* Equivalent to Go's byte() conversion.
|
|
843
|
+
* @param n The number to convert to a byte.
|
|
844
|
+
* @returns The byte value (0-255).
|
|
845
|
+
*/
|
|
846
|
+
export const byte = (n: number): number => {
|
|
847
|
+
return n & 0xff // Bitwise AND with 255 ensures we get a value in the range 0-255
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Accesses the byte value at a specific index of a UTF-8 encoded string.
|
|
852
|
+
* Mimics Go's string indexing behavior: `myString[index]`
|
|
853
|
+
* @param str The string to access.
|
|
854
|
+
* @param index The byte index.
|
|
855
|
+
* @returns The byte value (0-255) at the specified index.
|
|
856
|
+
* @throws Error if index is out of bounds.
|
|
857
|
+
*/
|
|
858
|
+
export const indexString = (str: string, index: number): number => {
|
|
859
|
+
const bytes = new TextEncoder().encode(str)
|
|
860
|
+
if (index < 0 || index >= bytes.length) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
`runtime error: index out of range [${index}] with length ${bytes.length}`,
|
|
863
|
+
)
|
|
864
|
+
}
|
|
865
|
+
return bytes[index]
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Returns the byte length of a string.
|
|
870
|
+
* Mimics Go's `len(string)` behavior.
|
|
871
|
+
* @param str The string.
|
|
872
|
+
* @returns The number of bytes in the UTF-8 representation of the string.
|
|
873
|
+
*/
|
|
874
|
+
export const stringLen = (str: string): number => {
|
|
875
|
+
return new TextEncoder().encode(str).length
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Slices a string based on byte indices.
|
|
880
|
+
* Mimics Go's string slicing behavior: `myString[low:high]` for valid UTF-8 slices only.
|
|
881
|
+
* @param str The string to slice.
|
|
882
|
+
* @param low The starting byte index (inclusive). Defaults to 0.
|
|
883
|
+
* @param high The ending byte index (exclusive). Defaults to string byte length.
|
|
884
|
+
* @returns The sliced string.
|
|
885
|
+
* @throws Error if the slice would create invalid UTF-8.
|
|
886
|
+
*/
|
|
887
|
+
export const sliceString = (
|
|
888
|
+
str: string,
|
|
889
|
+
low?: number,
|
|
890
|
+
high?: number,
|
|
891
|
+
): string => {
|
|
892
|
+
const bytes = new TextEncoder().encode(str)
|
|
893
|
+
const actualLow = low === undefined ? 0 : low
|
|
894
|
+
const actualHigh = high === undefined ? bytes.length : high
|
|
895
|
+
|
|
896
|
+
if (actualLow < 0 || actualHigh < actualLow || actualHigh > bytes.length) {
|
|
897
|
+
// Go's behavior for out-of-bounds slice on string is a panic.
|
|
898
|
+
// For simple slices like s[len(s):len(s)], it should produce an empty string.
|
|
899
|
+
// For s[len(s)+1:], it panics.
|
|
900
|
+
// Let's ensure high <= bytes.length and low <= high.
|
|
901
|
+
// If low == high, it's an empty string.
|
|
902
|
+
if (
|
|
903
|
+
actualLow === actualHigh &&
|
|
904
|
+
actualLow >= 0 &&
|
|
905
|
+
actualLow <= bytes.length
|
|
906
|
+
) {
|
|
907
|
+
return ''
|
|
908
|
+
}
|
|
909
|
+
throw new Error(
|
|
910
|
+
`runtime error: slice bounds out of range [${actualLow}:${actualHigh}] with length ${bytes.length}`,
|
|
911
|
+
)
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const slicedBytes = bytes.subarray(actualLow, actualHigh)
|
|
915
|
+
|
|
916
|
+
try {
|
|
917
|
+
// Attempt to decode with strict UTF-8 validation
|
|
918
|
+
const result = new TextDecoder('utf-8', { fatal: true }).decode(slicedBytes)
|
|
919
|
+
return result
|
|
920
|
+
} catch (e) {
|
|
921
|
+
// If we get here, the slice would create invalid UTF-8
|
|
922
|
+
// This is a fundamental limitation of JavaScript string handling
|
|
923
|
+
throw new Error(
|
|
924
|
+
`Cannot slice string at byte indices [${actualLow}:${actualHigh}] because it would create invalid UTF-8. ` +
|
|
925
|
+
`This is a limitation of JavaScript's string handling.`,
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Converts a Slice<number> (byte array) to a string using TextDecoder.
|
|
932
|
+
* @param bytes The Slice<number> to convert.
|
|
933
|
+
* @returns The resulting string.
|
|
934
|
+
*/
|
|
935
|
+
export const bytesToString = (
|
|
936
|
+
bytes: Slice<number> | Uint8Array | string,
|
|
937
|
+
): string => {
|
|
938
|
+
if (bytes === null) return ''
|
|
939
|
+
// If it's already a string, just return it
|
|
940
|
+
if (typeof bytes === 'string') return bytes
|
|
941
|
+
if (bytes instanceof Uint8Array) {
|
|
942
|
+
return new TextDecoder().decode(bytes)
|
|
943
|
+
}
|
|
944
|
+
// Ensure we get a plain number[] for Uint8Array.from
|
|
945
|
+
let byteArray: number[]
|
|
946
|
+
if (isComplexSlice(bytes)) {
|
|
947
|
+
// For complex slices, extract the relevant part of the backing array
|
|
948
|
+
byteArray = bytes.__meta__.backing.slice(
|
|
949
|
+
bytes.__meta__.offset,
|
|
950
|
+
bytes.__meta__.offset + bytes.__meta__.length,
|
|
951
|
+
)
|
|
952
|
+
} else {
|
|
953
|
+
// For simple T[] slices
|
|
954
|
+
byteArray = bytes
|
|
955
|
+
}
|
|
956
|
+
return new TextDecoder().decode(Uint8Array.from(byteArray))
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Converts a string to a Uint8Array (byte slice).
|
|
961
|
+
* @param s The input string.
|
|
962
|
+
* @returns A Uint8Array representing the UTF-8 bytes of the string.
|
|
963
|
+
*/
|
|
964
|
+
export function stringToBytes(s: string): Uint8Array {
|
|
965
|
+
return new TextEncoder().encode(s)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Handles string() conversion for values that could be either string or []byte.
|
|
970
|
+
* Used for generic type parameters with constraint []byte|string.
|
|
971
|
+
* @param value Value that is either a string or Uint8Array
|
|
972
|
+
* @returns The string representation
|
|
973
|
+
*/
|
|
974
|
+
export function genericBytesOrStringToString(
|
|
975
|
+
value: string | Uint8Array,
|
|
976
|
+
): string {
|
|
977
|
+
if (typeof value === 'string') {
|
|
978
|
+
return value
|
|
979
|
+
}
|
|
980
|
+
return bytesToString(value as unknown as number[])
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Indexes into a value that could be either a string or Uint8Array.
|
|
985
|
+
* Used for generic type parameters with constraint string | []byte.
|
|
986
|
+
* Both cases return a byte value (number).
|
|
987
|
+
* @param value Value that is either a string or Uint8Array
|
|
988
|
+
* @param index The index to access
|
|
989
|
+
* @returns The byte value at the specified index
|
|
990
|
+
*/
|
|
991
|
+
export function indexStringOrBytes(
|
|
992
|
+
value: string | Uint8Array,
|
|
993
|
+
index: number,
|
|
994
|
+
): number {
|
|
995
|
+
if (typeof value === 'string') {
|
|
996
|
+
return indexString(value, index)
|
|
997
|
+
} else {
|
|
998
|
+
// For Uint8Array, direct access returns the byte value
|
|
999
|
+
if (index < 0 || index >= value.length) {
|
|
1000
|
+
throw new Error(
|
|
1001
|
+
`runtime error: index out of range [${index}] with length ${value.length}`,
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
return value[index]
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Slices a value that could be either a string or Uint8Array.
|
|
1010
|
+
* Used for generic type parameters with constraint string | []byte.
|
|
1011
|
+
* @param value Value that is either a string or Uint8Array
|
|
1012
|
+
* @param low Starting index (inclusive). Defaults to 0.
|
|
1013
|
+
* @param high Ending index (exclusive). Defaults to length.
|
|
1014
|
+
* @param max Capacity limit (only used for Uint8Array, ignored for strings)
|
|
1015
|
+
* @returns The sliced value of the same type as input
|
|
1016
|
+
*/
|
|
1017
|
+
export function sliceStringOrBytes<T extends string | Uint8Array>(
|
|
1018
|
+
value: T,
|
|
1019
|
+
low?: number,
|
|
1020
|
+
high?: number,
|
|
1021
|
+
max?: number,
|
|
1022
|
+
): T {
|
|
1023
|
+
if (typeof value === 'string') {
|
|
1024
|
+
// For strings, use sliceString and ignore max parameter
|
|
1025
|
+
return sliceString(value, low, high) as T
|
|
1026
|
+
} else {
|
|
1027
|
+
// For Uint8Array, use goSlice
|
|
1028
|
+
return goSlice(value as Slice<number>, low, high, max) as T
|
|
1029
|
+
}
|
|
1030
|
+
}
|