goscript 0.0.83 → 0.1.0
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 +13 -1
- package/cmd/goscript/cmd_compile.go +70 -69
- package/cmd/goscript/cmd_compile_test.go +79 -0
- package/cmd/goscript/main.go +10 -5
- package/compiler/compile-request.go +218 -0
- package/compiler/compiler.go +16 -1336
- package/compiler/compliance_test.go +196 -0
- package/compiler/config.go +6 -13
- package/compiler/diagnostic.go +70 -0
- package/compiler/index.test.ts +28 -28
- package/compiler/index.ts +40 -72
- package/compiler/lowered-program.go +132 -0
- package/compiler/lowering.go +3576 -0
- package/compiler/override-registry.go +422 -0
- package/compiler/override-registry_test.go +207 -0
- package/compiler/package-graph.go +231 -0
- package/compiler/package-graph_test.go +281 -0
- package/compiler/result.go +13 -0
- package/compiler/runtime-contract.go +279 -0
- package/compiler/runtime-contract_test.go +90 -0
- package/compiler/semantic-model-types.go +110 -0
- package/compiler/semantic-model.go +922 -0
- package/compiler/semantic-model_test.go +416 -0
- package/compiler/service.go +133 -0
- package/compiler/skeleton_test.go +1145 -0
- package/compiler/typescript-emitter.go +663 -0
- package/compiler/wasm/compile.go +2 -3
- package/compiler/wasm/compile_test.go +29 -0
- package/compiler/wasm_api.go +10 -159
- package/dist/compiler/index.d.ts +1 -3
- package/dist/compiler/index.js +31 -55
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/builtin/builtin.d.ts +13 -0
- package/dist/gs/builtin/builtin.js +27 -7
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/channel.d.ts +3 -3
- package/dist/gs/builtin/channel.js.map +1 -1
- package/dist/gs/builtin/hostio.d.ts +86 -0
- package/dist/gs/builtin/hostio.js +266 -0
- package/dist/gs/builtin/hostio.js.map +1 -0
- package/dist/gs/builtin/index.d.ts +1 -0
- package/dist/gs/builtin/index.js +1 -0
- package/dist/gs/builtin/index.js.map +1 -1
- package/dist/gs/builtin/print.d.ts +8 -0
- package/dist/gs/builtin/print.js +111 -0
- package/dist/gs/builtin/print.js.map +1 -0
- package/dist/gs/builtin/slice.d.ts +1 -1
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.d.ts +11 -0
- package/dist/gs/builtin/type.js +55 -1
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/bytes/buffer.gs.js.map +1 -1
- package/dist/gs/bytes/bytes.gs.js.map +1 -1
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/crypto/rand/index.d.ts +5 -0
- package/dist/gs/crypto/rand/index.js +77 -0
- package/dist/gs/crypto/rand/index.js.map +1 -0
- package/dist/gs/encoding/json/index.d.ts +3 -0
- package/dist/gs/encoding/json/index.js +160 -0
- package/dist/gs/encoding/json/index.js.map +1 -0
- package/dist/gs/fmt/fmt.js +2 -22
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
- package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
- package/dist/gs/go/scanner/index.d.ts +29 -0
- package/dist/gs/go/scanner/index.js +120 -0
- package/dist/gs/go/scanner/index.js.map +1 -0
- package/dist/gs/go/token/index.d.ts +31 -0
- package/dist/gs/go/token/index.js +82 -0
- package/dist/gs/go/token/index.js.map +1 -0
- package/dist/gs/internal/abi/index.js.map +1 -1
- package/dist/gs/io/fs/fs.js.map +1 -1
- package/dist/gs/io/fs/readdir.js.map +1 -1
- package/dist/gs/io/fs/readfile.js.map +1 -1
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/os/dir_unix.gs.js.map +1 -1
- package/dist/gs/os/error.gs.js +2 -4
- package/dist/gs/os/error.gs.js.map +1 -1
- package/dist/gs/os/exec.gs.js.map +1 -1
- package/dist/gs/os/exec_posix.gs.js.map +1 -1
- package/dist/gs/os/rawconn_js.gs.js.map +1 -1
- package/dist/gs/os/root_js.gs.js.map +1 -1
- package/dist/gs/os/tempfile.gs.js +66 -9
- package/dist/gs/os/tempfile.gs.js.map +1 -1
- package/dist/gs/os/types.gs.js.map +1 -1
- package/dist/gs/os/types_js.gs.d.ts +2 -51
- package/dist/gs/os/types_js.gs.js +67 -105
- package/dist/gs/os/types_js.gs.js.map +1 -1
- package/dist/gs/os/types_unix.gs.js.map +1 -1
- package/dist/gs/path/filepath/match.js.map +1 -1
- package/dist/gs/path/match.js.map +1 -1
- package/dist/gs/path/path.js.map +1 -1
- package/dist/gs/reflect/index.d.ts +2 -2
- package/dist/gs/reflect/index.js +1 -1
- package/dist/gs/reflect/index.js.map +1 -1
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +2 -1
- package/dist/gs/reflect/type.js +85 -14
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/types.js.map +1 -1
- package/dist/gs/reflect/visiblefields.js.map +1 -1
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/sort/sort.gs.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/strconv/quote.gs.js.map +1 -1
- package/dist/gs/strings/builder.js.map +1 -1
- package/dist/gs/strings/reader.js.map +1 -1
- package/dist/gs/strings/replace.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/sync/atomic/value.gs.js.map +1 -1
- package/dist/gs/sync/sync.d.ts +1 -0
- package/dist/gs/sync/sync.js +12 -0
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/unicode.js.map +1 -1
- package/go.mod +2 -2
- package/gs/builtin/builtin.ts +31 -6
- package/gs/builtin/hostio.test.ts +246 -0
- package/gs/builtin/hostio.ts +413 -0
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/print.test.ts +48 -0
- package/gs/builtin/print.ts +154 -0
- package/gs/builtin/runtime-contract.test.ts +230 -0
- package/gs/builtin/type.ts +84 -1
- package/gs/crypto/rand/index.test.ts +32 -0
- package/gs/crypto/rand/index.ts +90 -0
- package/gs/crypto/rand/meta.json +5 -0
- package/gs/encoding/json/index.test.ts +65 -0
- package/gs/encoding/json/index.ts +186 -0
- package/gs/fmt/fmt.test.ts +41 -30
- package/gs/fmt/fmt.ts +2 -22
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
- package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
- package/gs/go/scanner/index.test.ts +50 -0
- package/gs/go/scanner/index.ts +157 -0
- package/gs/go/token/index.test.ts +21 -0
- package/gs/go/token/index.ts +120 -0
- package/gs/os/file_unix_js.test.ts +103 -0
- package/gs/os/meta.json +1 -2
- package/gs/os/tempfile.gs.test.ts +85 -0
- package/gs/os/tempfile.gs.ts +71 -11
- package/gs/os/types_js.gs.ts +74 -153
- package/gs/reflect/index.ts +1 -1
- package/gs/reflect/type.ts +106 -17
- package/gs/reflect/typefor.test.ts +75 -0
- package/gs/sync/sync.test.ts +24 -0
- package/gs/sync/sync.ts +12 -0
- package/package.json +13 -13
- package/compiler/analysis.go +0 -3475
- package/compiler/analysis_test.go +0 -338
- package/compiler/assignment.go +0 -580
- package/compiler/builtin_test.go +0 -92
- package/compiler/code-writer.go +0 -115
- package/compiler/compiler_test.go +0 -149
- package/compiler/composite-lit.go +0 -779
- package/compiler/config_test.go +0 -62
- package/compiler/constraint.go +0 -86
- package/compiler/decl.go +0 -801
- package/compiler/expr-call-async.go +0 -188
- package/compiler/expr-call-builtins.go +0 -208
- package/compiler/expr-call-helpers.go +0 -382
- package/compiler/expr-call-make.go +0 -318
- package/compiler/expr-call-type-conversion.go +0 -520
- package/compiler/expr-call.go +0 -413
- package/compiler/expr-selector.go +0 -343
- package/compiler/expr-star.go +0 -82
- package/compiler/expr-type.go +0 -442
- package/compiler/expr-value.go +0 -89
- package/compiler/expr.go +0 -773
- package/compiler/field.go +0 -183
- package/compiler/gs_dependencies_test.go +0 -298
- package/compiler/lit.go +0 -322
- package/compiler/output.go +0 -72
- package/compiler/primitive.go +0 -149
- package/compiler/protobuf.go +0 -697
- package/compiler/sanitize.go +0 -100
- package/compiler/spec-struct.go +0 -995
- package/compiler/spec-value.go +0 -540
- package/compiler/spec.go +0 -725
- package/compiler/stmt-assign.go +0 -664
- package/compiler/stmt-for.go +0 -266
- package/compiler/stmt-range.go +0 -475
- package/compiler/stmt-select.go +0 -262
- package/compiler/stmt-type-switch.go +0 -147
- package/compiler/stmt.go +0 -1308
- package/compiler/type-assert.go +0 -386
- package/compiler/type-info.go +0 -156
- package/compiler/type-utils.go +0 -207
- package/compiler/type.go +0 -892
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
byte,
|
|
5
|
+
callGenericMethod,
|
|
6
|
+
chanRecvWithOk,
|
|
7
|
+
functionValue,
|
|
8
|
+
genericZero,
|
|
9
|
+
int,
|
|
10
|
+
interfaceValue,
|
|
11
|
+
makeChannel,
|
|
12
|
+
makeMap,
|
|
13
|
+
mapGet,
|
|
14
|
+
mapSet,
|
|
15
|
+
markAsStructValue,
|
|
16
|
+
namedFunction,
|
|
17
|
+
newError,
|
|
18
|
+
pointerValue,
|
|
19
|
+
print,
|
|
20
|
+
println,
|
|
21
|
+
registerInterfaceType,
|
|
22
|
+
registerStructType,
|
|
23
|
+
resetHostRuntimeForTests,
|
|
24
|
+
TypeKind,
|
|
25
|
+
typeAssert,
|
|
26
|
+
typedNil,
|
|
27
|
+
unref,
|
|
28
|
+
varRef,
|
|
29
|
+
} from './index.js'
|
|
30
|
+
|
|
31
|
+
const originalDeno = (globalThis as any).Deno
|
|
32
|
+
const originalProcess = (globalThis as any).process
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
vi.restoreAllMocks()
|
|
36
|
+
|
|
37
|
+
if (originalDeno === undefined) {
|
|
38
|
+
delete (globalThis as any).Deno
|
|
39
|
+
} else {
|
|
40
|
+
;(globalThis as any).Deno = originalDeno
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (originalProcess === undefined) {
|
|
44
|
+
delete (globalThis as any).process
|
|
45
|
+
} else {
|
|
46
|
+
;(globalThis as any).process = originalProcess
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
resetHostRuntimeForTests()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('builtin runtime contract helpers', () => {
|
|
53
|
+
it('writes print and println through the host runtime owner', () => {
|
|
54
|
+
const writes: Array<{ fd: number; text: string }> = []
|
|
55
|
+
const writeSync = vi.fn(
|
|
56
|
+
(
|
|
57
|
+
fd: number,
|
|
58
|
+
buffer: Uint8Array,
|
|
59
|
+
_offset?: number,
|
|
60
|
+
length?: number,
|
|
61
|
+
_position?: number | null,
|
|
62
|
+
) => {
|
|
63
|
+
writes.push({
|
|
64
|
+
fd,
|
|
65
|
+
text: new TextDecoder().decode(
|
|
66
|
+
buffer.subarray(0, length ?? buffer.length),
|
|
67
|
+
),
|
|
68
|
+
})
|
|
69
|
+
return length ?? buffer.length
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
delete (globalThis as any).Deno
|
|
74
|
+
;(globalThis as any).process = {
|
|
75
|
+
getBuiltinModule: vi.fn(() => ({
|
|
76
|
+
readSync: vi.fn(),
|
|
77
|
+
writeSync,
|
|
78
|
+
})),
|
|
79
|
+
}
|
|
80
|
+
resetHostRuntimeForTests()
|
|
81
|
+
|
|
82
|
+
print('value:', 3)
|
|
83
|
+
println('done')
|
|
84
|
+
|
|
85
|
+
expect(writes).toEqual([
|
|
86
|
+
{ fd: 1, text: 'value: 3' },
|
|
87
|
+
{ fd: 1, text: 'done\n' },
|
|
88
|
+
])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('exposes numeric, varref, map, and error helpers', () => {
|
|
92
|
+
expect(int(1.9)).toBe(1)
|
|
93
|
+
expect(byte(257)).toBe(1)
|
|
94
|
+
|
|
95
|
+
const value = varRef(4)
|
|
96
|
+
value.value = 8
|
|
97
|
+
expect(unref(value)).toBe(8)
|
|
98
|
+
expect(pointerValue(value)).toBe(8)
|
|
99
|
+
expect(pointerValue({ ok: true })).toEqual({ ok: true })
|
|
100
|
+
expect(() => pointerValue(null)).toThrow('nil pointer dereference')
|
|
101
|
+
|
|
102
|
+
const m = makeMap<string, number>()
|
|
103
|
+
mapSet(m, 'answer', 42)
|
|
104
|
+
expect(mapGet(m, 'answer', 0)).toEqual([42, true])
|
|
105
|
+
expect(mapGet(m, 'missing', 0)).toEqual([0, false])
|
|
106
|
+
|
|
107
|
+
expect(newError('bad')?.Error()).toBe('bad')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('exposes value and type descriptor helpers', () => {
|
|
111
|
+
class Runner {
|
|
112
|
+
public Run(): string {
|
|
113
|
+
return 'ok'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const runnerType = registerStructType(
|
|
118
|
+
'phase5.Runner',
|
|
119
|
+
markAsStructValue(new Runner()),
|
|
120
|
+
[{ name: 'Run', args: [], returns: [{ type: 'string' }] }],
|
|
121
|
+
Runner,
|
|
122
|
+
)
|
|
123
|
+
const runnerInterface = registerInterfaceType(
|
|
124
|
+
'phase5.RunnerInterface',
|
|
125
|
+
null,
|
|
126
|
+
[{ name: 'Run', args: [], returns: [{ type: 'string' }] }],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const value = new Runner()
|
|
130
|
+
expect(markAsStructValue(value)).toBe(value)
|
|
131
|
+
expect(typeAssert<Runner>(value, runnerType)).toEqual({
|
|
132
|
+
ok: true,
|
|
133
|
+
value,
|
|
134
|
+
})
|
|
135
|
+
expect(typeAssert<Runner>(new Runner(), runnerInterface).ok).toBe(true)
|
|
136
|
+
expect(typeAssert<Runner>(null, runnerInterface).ok).toBe(false)
|
|
137
|
+
|
|
138
|
+
const nil = typedNil('*main.Example')
|
|
139
|
+
expect(nil.__isTypedNil).toBe(true)
|
|
140
|
+
expect(nil.__goType).toBe('*main.Example')
|
|
141
|
+
expect(
|
|
142
|
+
typeAssert<Runner | null>(typedNil('*phase5.Runner'), {
|
|
143
|
+
kind: TypeKind.Pointer,
|
|
144
|
+
elemType: 'phase5.Runner',
|
|
145
|
+
}),
|
|
146
|
+
).toEqual({ value: null, ok: true })
|
|
147
|
+
expect(TypeKind.Pointer).toBe('pointer')
|
|
148
|
+
|
|
149
|
+
class TypedDog {
|
|
150
|
+
public Name(this: TypedDog | null): string {
|
|
151
|
+
if (this === null) {
|
|
152
|
+
return 'unknown dog'
|
|
153
|
+
}
|
|
154
|
+
return 'dog'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
registerStructType(
|
|
159
|
+
'phase5.TypedDog',
|
|
160
|
+
new TypedDog(),
|
|
161
|
+
[{ name: 'Name', args: [], returns: [{ type: 'string' }] }],
|
|
162
|
+
TypedDog,
|
|
163
|
+
)
|
|
164
|
+
const dogInterface = registerInterfaceType(
|
|
165
|
+
'phase5.DogInterface',
|
|
166
|
+
null,
|
|
167
|
+
[{ name: 'Name', args: [], returns: [{ type: 'string' }] }],
|
|
168
|
+
)
|
|
169
|
+
const nilDog = interfaceValue<{ Name(): string } | null>(
|
|
170
|
+
null,
|
|
171
|
+
'*phase5.TypedDog',
|
|
172
|
+
)
|
|
173
|
+
expect(nilDog).not.toBeNull()
|
|
174
|
+
expect(nilDog!.Name()).toBe('unknown dog')
|
|
175
|
+
expect(typeAssert<{ Name(): string }>(nilDog, dogInterface).ok).toBe(true)
|
|
176
|
+
expect(
|
|
177
|
+
typeAssert<TypedDog | null>(nilDog, {
|
|
178
|
+
kind: TypeKind.Pointer,
|
|
179
|
+
elemType: 'phase5.TypedDog',
|
|
180
|
+
}),
|
|
181
|
+
).toEqual({ value: null, ok: true })
|
|
182
|
+
|
|
183
|
+
const greet = namedFunction((name: string) => `hello ${name}`, 'phase5.Greet')
|
|
184
|
+
expect(typeAssert<typeof greet>(greet, {
|
|
185
|
+
kind: TypeKind.Function,
|
|
186
|
+
name: 'phase5.Greet',
|
|
187
|
+
}).ok).toBe(true)
|
|
188
|
+
expect(
|
|
189
|
+
typeAssert<{ Name: string }>(
|
|
190
|
+
{ Name: 'Alice' },
|
|
191
|
+
{
|
|
192
|
+
kind: TypeKind.Struct,
|
|
193
|
+
methods: [],
|
|
194
|
+
fields: {
|
|
195
|
+
Name: {
|
|
196
|
+
type: { kind: TypeKind.Basic, name: 'string' },
|
|
197
|
+
tag: 'json:"name"',
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
).ok,
|
|
202
|
+
).toBe(true)
|
|
203
|
+
const literal = functionValue((value: number) => String(value), {
|
|
204
|
+
kind: TypeKind.Function,
|
|
205
|
+
params: [{ kind: TypeKind.Basic, name: 'int' }],
|
|
206
|
+
results: [{ kind: TypeKind.Basic, name: 'string' }],
|
|
207
|
+
})
|
|
208
|
+
expect(literal(7)).toBe('7')
|
|
209
|
+
expect(literal).toHaveProperty('__typeInfo')
|
|
210
|
+
|
|
211
|
+
const genericArgs = {
|
|
212
|
+
T: {
|
|
213
|
+
zero: () => 0,
|
|
214
|
+
methods: {
|
|
215
|
+
String: (value: number) => String(value),
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
expect(genericZero(genericArgs, 'T', null)).toBe(0)
|
|
220
|
+
expect(callGenericMethod(genericArgs, 'T', 'String', 12)).toBe('12')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('exposes channel helpers used by future lowering', async () => {
|
|
224
|
+
const channel = makeChannel<number>(1, 0, 'both')
|
|
225
|
+
await channel.send(7)
|
|
226
|
+
expect(await chanRecvWithOk(channel)).toEqual({ value: 7, ok: true })
|
|
227
|
+
channel.close()
|
|
228
|
+
expect(await chanRecvWithOk(channel)).toEqual({ value: 0, ok: false })
|
|
229
|
+
})
|
|
230
|
+
})
|
package/gs/builtin/type.ts
CHANGED
|
@@ -500,9 +500,12 @@ function matchesStructType(value: any, info: TypeInfo): boolean {
|
|
|
500
500
|
|
|
501
501
|
if (fieldsExist && sameFieldCount && allFieldsInStruct) {
|
|
502
502
|
return Object.entries(info.fields).every(([fieldName, fieldType]) => {
|
|
503
|
+
const fieldTypeInfo = isStructFieldInfo(fieldType)
|
|
504
|
+
? fieldType.type
|
|
505
|
+
: fieldType
|
|
503
506
|
return matchesType(
|
|
504
507
|
value[fieldName],
|
|
505
|
-
normalizeTypeInfo(
|
|
508
|
+
normalizeTypeInfo(fieldTypeInfo),
|
|
506
509
|
)
|
|
507
510
|
})
|
|
508
511
|
}
|
|
@@ -1008,6 +1011,9 @@ export function typeAssert<T>(
|
|
|
1008
1011
|
|
|
1009
1012
|
// Handle typed nil pointers (created by typedNil() for conversions like (*T)(nil))
|
|
1010
1013
|
if (typeof value === 'object' && value !== null && value.__isTypedNil) {
|
|
1014
|
+
if (isInterfaceTypeInfo(normalizedType) && matchesInterfaceType(value, normalizedType)) {
|
|
1015
|
+
return { value: value as T, ok: true }
|
|
1016
|
+
}
|
|
1011
1017
|
// For typed nils, we need to compare the stored type with the expected type
|
|
1012
1018
|
if (isPointerTypeInfo(normalizedType)) {
|
|
1013
1019
|
// Parse the stored type string and compare with expected type
|
|
@@ -1219,3 +1225,80 @@ export function typedNil(typeName: string): any {
|
|
|
1219
1225
|
__isTypedNil: true,
|
|
1220
1226
|
})
|
|
1221
1227
|
}
|
|
1228
|
+
|
|
1229
|
+
export function interfaceValue<T>(value: unknown, typeName: string): T {
|
|
1230
|
+
if (value !== null && value !== undefined) {
|
|
1231
|
+
return value as T
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const nilValue = typedNil(typeName)
|
|
1235
|
+
if (!typeName.startsWith('*')) {
|
|
1236
|
+
return nilValue as T
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const dynamicType = typeRegistry.get(typeName.slice(1))
|
|
1240
|
+
if (!dynamicType || !isStructTypeInfo(dynamicType) || !dynamicType.ctor) {
|
|
1241
|
+
return nilValue as T
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const prototype = dynamicType.ctor.prototype as Record<string, unknown>
|
|
1245
|
+
for (const method of dynamicType.methods) {
|
|
1246
|
+
const implementation = prototype[method.name]
|
|
1247
|
+
if (typeof implementation !== 'function') {
|
|
1248
|
+
continue
|
|
1249
|
+
}
|
|
1250
|
+
Object.defineProperty(nilValue, method.name, {
|
|
1251
|
+
value: (...args: unknown[]) => implementation.call(null, ...args),
|
|
1252
|
+
enumerable: true,
|
|
1253
|
+
})
|
|
1254
|
+
}
|
|
1255
|
+
return nilValue as T
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export function namedFunction<T>(fn: T, typeName: string): T {
|
|
1259
|
+
if (typeof fn !== 'function') {
|
|
1260
|
+
return fn
|
|
1261
|
+
}
|
|
1262
|
+
return Object.assign(fn, { __goTypeName: typeName })
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
export function functionValue<T extends (...args: any[]) => any>(
|
|
1266
|
+
fn: T,
|
|
1267
|
+
typeInfo: FunctionTypeInfo,
|
|
1268
|
+
): T {
|
|
1269
|
+
return Object.assign(fn, { __typeInfo: typeInfo })
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
export interface GenericTypeDescriptor<T = any> {
|
|
1273
|
+
type?: TypeInfo | string
|
|
1274
|
+
zero?: () => T
|
|
1275
|
+
methods?: Record<string, (receiver: T, ...args: any[]) => any>
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
export type GenericTypeArgs = Record<string, GenericTypeDescriptor>
|
|
1279
|
+
|
|
1280
|
+
export function genericZero<T>(
|
|
1281
|
+
typeArgs: GenericTypeArgs | undefined,
|
|
1282
|
+
name: string,
|
|
1283
|
+
fallback: T,
|
|
1284
|
+
): T {
|
|
1285
|
+
const zero = typeArgs?.[name]?.zero
|
|
1286
|
+
if (zero) {
|
|
1287
|
+
return zero() as T
|
|
1288
|
+
}
|
|
1289
|
+
return fallback
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
export function callGenericMethod<T>(
|
|
1293
|
+
typeArgs: GenericTypeArgs | undefined,
|
|
1294
|
+
name: string,
|
|
1295
|
+
method: string,
|
|
1296
|
+
receiver: T,
|
|
1297
|
+
...args: any[]
|
|
1298
|
+
): any {
|
|
1299
|
+
const fn = typeArgs?.[name]?.methods?.[method]
|
|
1300
|
+
if (fn) {
|
|
1301
|
+
return fn(receiver, ...args)
|
|
1302
|
+
}
|
|
1303
|
+
return (receiver as any)[method](...args)
|
|
1304
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
4
|
+
|
|
5
|
+
import { Read, Reader, Text } from './index.js'
|
|
6
|
+
|
|
7
|
+
describe('crypto/rand override', () => {
|
|
8
|
+
it('fills byte slices from Web Crypto', () => {
|
|
9
|
+
const buf = new Uint8Array(32)
|
|
10
|
+
const [n, err] = Read(buf)
|
|
11
|
+
|
|
12
|
+
expect(err).toBeNull()
|
|
13
|
+
expect(n).toBe(32)
|
|
14
|
+
expect(buf.some((b) => b !== 0)).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('exposes Reader as an io.Reader-compatible source', () => {
|
|
18
|
+
const buf = $.makeSlice<number>(12, 12, 'byte')
|
|
19
|
+
const [n, err] = Reader.Read(buf)
|
|
20
|
+
|
|
21
|
+
expect(err).toBeNull()
|
|
22
|
+
expect(n).toBe(12)
|
|
23
|
+
expect(Array.from(buf ?? []).some((b) => b !== 0)).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('generates base32 text tokens', () => {
|
|
27
|
+
const token = Text()
|
|
28
|
+
|
|
29
|
+
expect(token).toHaveLength(26)
|
|
30
|
+
expect(token).toMatch(/^[A-Z2-7]+$/)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
2
|
+
import * as io from '@goscript/io/index.js'
|
|
3
|
+
|
|
4
|
+
const base32alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
|
5
|
+
const maxGetRandomValuesBytes = 65536
|
|
6
|
+
|
|
7
|
+
class RandError {
|
|
8
|
+
constructor(private readonly message: string) {}
|
|
9
|
+
|
|
10
|
+
Error(): string {
|
|
11
|
+
return this.message
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class WebCryptoReader implements io.Reader {
|
|
16
|
+
Read(p: $.Bytes): [number, $.GoError] {
|
|
17
|
+
const err = fillSecureBytes(p)
|
|
18
|
+
if (err != null) {
|
|
19
|
+
return [0, err]
|
|
20
|
+
}
|
|
21
|
+
return [$.len(p), null]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export let Reader: io.Reader = new WebCryptoReader()
|
|
26
|
+
|
|
27
|
+
export function Read(b: $.Bytes): [number, $.GoError] {
|
|
28
|
+
const [n, err] = Reader.Read(b)
|
|
29
|
+
if (err != null) {
|
|
30
|
+
return [n, err]
|
|
31
|
+
}
|
|
32
|
+
if (n !== $.len(b)) {
|
|
33
|
+
return [n, io.ErrUnexpectedEOF]
|
|
34
|
+
}
|
|
35
|
+
return [n, null]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function Text(): string {
|
|
39
|
+
const src = new Uint8Array(26)
|
|
40
|
+
const [, err] = Read(src)
|
|
41
|
+
if (err != null) {
|
|
42
|
+
throw new Error(err.Error())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let out = ''
|
|
46
|
+
for (const b of src) {
|
|
47
|
+
out += base32alphabet[b % 32]
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fillSecureBytes(dst: $.Bytes): $.GoError {
|
|
53
|
+
const length = $.len(dst)
|
|
54
|
+
if (length === 0) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const crypto = secureCrypto()
|
|
59
|
+
if (crypto == null) {
|
|
60
|
+
return new RandError('crypto/rand: Web Crypto getRandomValues is unavailable')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (dst instanceof Uint8Array) {
|
|
64
|
+
fillUint8Array(crypto, dst)
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tmp = new Uint8Array(length)
|
|
69
|
+
fillUint8Array(crypto, tmp)
|
|
70
|
+
$.copy(dst, tmp)
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function fillUint8Array(crypto: Crypto, dst: Uint8Array): void {
|
|
75
|
+
for (let offset = 0; offset < dst.length; offset += maxGetRandomValuesBytes) {
|
|
76
|
+
const chunk = dst.subarray(
|
|
77
|
+
offset,
|
|
78
|
+
Math.min(offset + maxGetRandomValuesBytes, dst.length),
|
|
79
|
+
) as Uint8Array<ArrayBuffer>
|
|
80
|
+
crypto.getRandomValues(chunk)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function secureCrypto(): Crypto | null {
|
|
85
|
+
const crypto = globalThis.crypto
|
|
86
|
+
if (crypto && typeof crypto.getRandomValues === 'function') {
|
|
87
|
+
return crypto
|
|
88
|
+
}
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
4
|
+
|
|
5
|
+
import { Marshal, Unmarshal } from './index.js'
|
|
6
|
+
|
|
7
|
+
class Person {
|
|
8
|
+
public _fields = {
|
|
9
|
+
Name: $.varRef(''),
|
|
10
|
+
Age: $.varRef(0),
|
|
11
|
+
Active: $.varRef(false),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static __typeInfo = $.registerStructType(
|
|
15
|
+
'test.Person',
|
|
16
|
+
new Person(),
|
|
17
|
+
[],
|
|
18
|
+
Person,
|
|
19
|
+
{
|
|
20
|
+
Name: { type: { kind: $.TypeKind.Basic, name: 'string' }, tag: 'json:"name"' },
|
|
21
|
+
Age: { type: { kind: $.TypeKind.Basic, name: 'int' }, tag: 'json:"age"' },
|
|
22
|
+
Active: { type: { kind: $.TypeKind.Basic, name: 'bool' }, tag: 'json:"active"' },
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('encoding/json override', () => {
|
|
28
|
+
it('marshals struct fields through json tags', () => {
|
|
29
|
+
const person = new Person()
|
|
30
|
+
person._fields.Name.value = 'Alice'
|
|
31
|
+
person._fields.Age.value = 30
|
|
32
|
+
person._fields.Active.value = true
|
|
33
|
+
|
|
34
|
+
const [data, err] = Marshal(person)
|
|
35
|
+
|
|
36
|
+
expect(err).toBeNull()
|
|
37
|
+
expect($.bytesToString(data)).toBe(
|
|
38
|
+
'{"name":"Alice","age":30,"active":true}',
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('unmarshals into struct and map pointers', () => {
|
|
43
|
+
const person = $.varRef(new Person())
|
|
44
|
+
const personErr = Unmarshal(
|
|
45
|
+
$.stringToBytes('{"name":"Bob","age":25,"active":false}'),
|
|
46
|
+
person,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
expect(personErr).toBeNull()
|
|
50
|
+
expect(person.value._fields.Name.value).toBe('Bob')
|
|
51
|
+
expect(person.value._fields.Age.value).toBe(25)
|
|
52
|
+
expect(person.value._fields.Active.value).toBe(false)
|
|
53
|
+
|
|
54
|
+
const mapRef: $.VarRef<Map<string, unknown> | null> = $.varRef(null)
|
|
55
|
+
const mapErr = Unmarshal(
|
|
56
|
+
$.stringToBytes('{"name":"Carol","age":22,"active":true}'),
|
|
57
|
+
mapRef,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(mapErr).toBeNull()
|
|
61
|
+
expect(mapRef.value?.get('name')).toBe('Carol')
|
|
62
|
+
expect(mapRef.value?.get('age')).toBe(22)
|
|
63
|
+
expect(mapRef.value?.get('active')).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
2
|
+
|
|
3
|
+
export function Marshal(v: unknown): [$.Slice<number>, $.GoError] {
|
|
4
|
+
try {
|
|
5
|
+
return [$.stringToBytes(JSON.stringify(marshalValue(v))), null]
|
|
6
|
+
} catch (err) {
|
|
7
|
+
return [null, goError(err)]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Unmarshal(data: $.Slice<number>, v: unknown): $.GoError {
|
|
12
|
+
try {
|
|
13
|
+
assignDecodedValue(v, JSON.parse($.bytesToString(data)))
|
|
14
|
+
return null
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return goError(err)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function marshalValue(v: unknown): unknown {
|
|
21
|
+
if ($.isVarRef(v)) {
|
|
22
|
+
return marshalValue(v.value)
|
|
23
|
+
}
|
|
24
|
+
if (v === null || v === undefined) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
if (typeof v !== 'object') {
|
|
28
|
+
return v
|
|
29
|
+
}
|
|
30
|
+
if (v instanceof Uint8Array) {
|
|
31
|
+
return Array.from(v).map(marshalValue)
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(v)) {
|
|
34
|
+
return v.map(marshalValue)
|
|
35
|
+
}
|
|
36
|
+
if (v instanceof Map) {
|
|
37
|
+
const out: Record<string, unknown> = {}
|
|
38
|
+
for (const [key, value] of v.entries()) {
|
|
39
|
+
out[String(key)] = marshalValue(value)
|
|
40
|
+
}
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
43
|
+
if (!isStructValue(v)) {
|
|
44
|
+
return v
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const out: Record<string, unknown> = {}
|
|
48
|
+
const typeFields = structFieldMetadata(v)
|
|
49
|
+
for (const [fieldName, ref] of Object.entries(v._fields)) {
|
|
50
|
+
const jsonName = jsonFieldName(fieldName, typeFields[fieldName]?.tag)
|
|
51
|
+
if (jsonName === '') {
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
out[jsonName] = marshalValue(ref.value)
|
|
55
|
+
}
|
|
56
|
+
return out
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assignDecodedValue(target: unknown, decoded: unknown): void {
|
|
60
|
+
if ($.isVarRef(target)) {
|
|
61
|
+
if (isStructValue(target.value) && isPlainObject(decoded)) {
|
|
62
|
+
assignStructFields(target.value, decoded)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (isPlainObject(decoded)) {
|
|
66
|
+
target.value = objectToMap(decoded)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
target.value = decoded
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (isStructValue(target) && isPlainObject(decoded)) {
|
|
73
|
+
assignStructFields(target, decoded)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assignStructFields(
|
|
78
|
+
target: { _fields: Record<string, $.VarRef<unknown>> },
|
|
79
|
+
decoded: Record<string, unknown>,
|
|
80
|
+
): void {
|
|
81
|
+
const typeFields = structFieldMetadata(target)
|
|
82
|
+
for (const [fieldName, ref] of Object.entries(target._fields)) {
|
|
83
|
+
const jsonName = jsonFieldName(fieldName, typeFields[fieldName]?.tag)
|
|
84
|
+
if (
|
|
85
|
+
jsonName !== '' &&
|
|
86
|
+
Object.prototype.hasOwnProperty.call(decoded, jsonName)
|
|
87
|
+
) {
|
|
88
|
+
ref.value = decoded[jsonName]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function objectToMap(decoded: Record<string, unknown>): Map<string, unknown> {
|
|
94
|
+
const out = new Map<string, unknown>()
|
|
95
|
+
for (const [key, value] of Object.entries(decoded)) {
|
|
96
|
+
out.set(key, isPlainObject(value) ? objectToMap(value) : value)
|
|
97
|
+
}
|
|
98
|
+
return out
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isStructValue(
|
|
102
|
+
value: unknown,
|
|
103
|
+
): value is { _fields: Record<string, $.VarRef<unknown>> } {
|
|
104
|
+
if (value === null || typeof value !== 'object') {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
const fields = Reflect.get(value, '_fields')
|
|
108
|
+
return (
|
|
109
|
+
fields !== null &&
|
|
110
|
+
fields !== undefined &&
|
|
111
|
+
typeof fields === 'object' &&
|
|
112
|
+
!Array.isArray(fields)
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
117
|
+
return (
|
|
118
|
+
value !== null &&
|
|
119
|
+
typeof value === 'object' &&
|
|
120
|
+
!Array.isArray(value) &&
|
|
121
|
+
!(value instanceof Uint8Array) &&
|
|
122
|
+
!(value instanceof Map)
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function structFieldMetadata(
|
|
127
|
+
value: unknown,
|
|
128
|
+
): Record<string, { tag?: string }> {
|
|
129
|
+
if (value === null || typeof value !== 'object') {
|
|
130
|
+
return {}
|
|
131
|
+
}
|
|
132
|
+
const ctor = Reflect.get(value, 'constructor')
|
|
133
|
+
if (
|
|
134
|
+
ctor === null ||
|
|
135
|
+
ctor === undefined ||
|
|
136
|
+
(typeof ctor !== 'object' && typeof ctor !== 'function')
|
|
137
|
+
) {
|
|
138
|
+
return {}
|
|
139
|
+
}
|
|
140
|
+
const typeInfo = Reflect.get(ctor, '__typeInfo')
|
|
141
|
+
if (
|
|
142
|
+
typeInfo === null ||
|
|
143
|
+
typeInfo === undefined ||
|
|
144
|
+
typeof typeInfo !== 'object'
|
|
145
|
+
) {
|
|
146
|
+
return {}
|
|
147
|
+
}
|
|
148
|
+
const fields = Reflect.get(typeInfo, 'fields')
|
|
149
|
+
if (isPlainObject(fields)) {
|
|
150
|
+
const out: Record<string, { tag?: string }> = {}
|
|
151
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
152
|
+
if (!isPlainObject(field)) {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
const tag = field.tag
|
|
156
|
+
out[name] = typeof tag === 'string' ? { tag } : {}
|
|
157
|
+
}
|
|
158
|
+
return out
|
|
159
|
+
}
|
|
160
|
+
return {}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function goError(err: unknown): $.GoError {
|
|
164
|
+
if (err instanceof Error) {
|
|
165
|
+
return $.toGoError(err)
|
|
166
|
+
}
|
|
167
|
+
return $.newError(String(err))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function jsonFieldName(fieldName: string, tag: string | undefined): string {
|
|
171
|
+
if (tag === undefined || !tag.startsWith('json:"')) {
|
|
172
|
+
return fieldName
|
|
173
|
+
}
|
|
174
|
+
const end = tag.indexOf('"', 'json:"'.length)
|
|
175
|
+
if (end < 0) {
|
|
176
|
+
return fieldName
|
|
177
|
+
}
|
|
178
|
+
const name = tag.slice('json:"'.length, end).split(',')[0]
|
|
179
|
+
if (name === '-') {
|
|
180
|
+
return ''
|
|
181
|
+
}
|
|
182
|
+
if (name === '') {
|
|
183
|
+
return fieldName
|
|
184
|
+
}
|
|
185
|
+
return name
|
|
186
|
+
}
|