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.
Files changed (197) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +27 -7
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +86 -0
  39. package/dist/gs/builtin/hostio.js +266 -0
  40. package/dist/gs/builtin/hostio.js.map +1 -0
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/print.d.ts +8 -0
  45. package/dist/gs/builtin/print.js +111 -0
  46. package/dist/gs/builtin/print.js.map +1 -0
  47. package/dist/gs/builtin/slice.d.ts +1 -1
  48. package/dist/gs/builtin/slice.js.map +1 -1
  49. package/dist/gs/builtin/type.d.ts +11 -0
  50. package/dist/gs/builtin/type.js +55 -1
  51. package/dist/gs/builtin/type.js.map +1 -1
  52. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  53. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  54. package/dist/gs/bytes/reader.gs.js.map +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/crypto/rand/index.d.ts +5 -0
  57. package/dist/gs/crypto/rand/index.js +77 -0
  58. package/dist/gs/crypto/rand/index.js.map +1 -0
  59. package/dist/gs/encoding/json/index.d.ts +3 -0
  60. package/dist/gs/encoding/json/index.js +160 -0
  61. package/dist/gs/encoding/json/index.js.map +1 -0
  62. package/dist/gs/fmt/fmt.js +2 -22
  63. package/dist/gs/fmt/fmt.js.map +1 -1
  64. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  65. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  66. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  67. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  68. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  69. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  70. package/dist/gs/go/scanner/index.d.ts +29 -0
  71. package/dist/gs/go/scanner/index.js +120 -0
  72. package/dist/gs/go/scanner/index.js.map +1 -0
  73. package/dist/gs/go/token/index.d.ts +31 -0
  74. package/dist/gs/go/token/index.js +82 -0
  75. package/dist/gs/go/token/index.js.map +1 -0
  76. package/dist/gs/internal/abi/index.js.map +1 -1
  77. package/dist/gs/io/fs/fs.js.map +1 -1
  78. package/dist/gs/io/fs/readdir.js.map +1 -1
  79. package/dist/gs/io/fs/readfile.js.map +1 -1
  80. package/dist/gs/io/fs/stat.js.map +1 -1
  81. package/dist/gs/io/fs/sub.js.map +1 -1
  82. package/dist/gs/io/io.js.map +1 -1
  83. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  84. package/dist/gs/os/error.gs.js +2 -4
  85. package/dist/gs/os/error.gs.js.map +1 -1
  86. package/dist/gs/os/exec.gs.js.map +1 -1
  87. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  88. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  89. package/dist/gs/os/root_js.gs.js.map +1 -1
  90. package/dist/gs/os/tempfile.gs.js +66 -9
  91. package/dist/gs/os/tempfile.gs.js.map +1 -1
  92. package/dist/gs/os/types.gs.js.map +1 -1
  93. package/dist/gs/os/types_js.gs.d.ts +2 -51
  94. package/dist/gs/os/types_js.gs.js +67 -105
  95. package/dist/gs/os/types_js.gs.js.map +1 -1
  96. package/dist/gs/os/types_unix.gs.js.map +1 -1
  97. package/dist/gs/path/filepath/match.js.map +1 -1
  98. package/dist/gs/path/match.js.map +1 -1
  99. package/dist/gs/path/path.js.map +1 -1
  100. package/dist/gs/reflect/index.d.ts +2 -2
  101. package/dist/gs/reflect/index.js +1 -1
  102. package/dist/gs/reflect/index.js.map +1 -1
  103. package/dist/gs/reflect/map.js.map +1 -1
  104. package/dist/gs/reflect/type.d.ts +2 -1
  105. package/dist/gs/reflect/type.js +85 -14
  106. package/dist/gs/reflect/type.js.map +1 -1
  107. package/dist/gs/reflect/types.js.map +1 -1
  108. package/dist/gs/reflect/visiblefields.js.map +1 -1
  109. package/dist/gs/runtime/runtime.js.map +1 -1
  110. package/dist/gs/sort/sort.gs.js.map +1 -1
  111. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  112. package/dist/gs/strconv/quote.gs.js.map +1 -1
  113. package/dist/gs/strings/builder.js.map +1 -1
  114. package/dist/gs/strings/reader.js.map +1 -1
  115. package/dist/gs/strings/replace.js.map +1 -1
  116. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  117. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  118. package/dist/gs/sync/sync.d.ts +1 -0
  119. package/dist/gs/sync/sync.js +12 -0
  120. package/dist/gs/sync/sync.js.map +1 -1
  121. package/dist/gs/time/time.js.map +1 -1
  122. package/dist/gs/unicode/unicode.js.map +1 -1
  123. package/go.mod +2 -2
  124. package/gs/builtin/builtin.ts +31 -6
  125. package/gs/builtin/hostio.test.ts +246 -0
  126. package/gs/builtin/hostio.ts +413 -0
  127. package/gs/builtin/index.ts +1 -0
  128. package/gs/builtin/print.test.ts +48 -0
  129. package/gs/builtin/print.ts +154 -0
  130. package/gs/builtin/runtime-contract.test.ts +230 -0
  131. package/gs/builtin/type.ts +84 -1
  132. package/gs/crypto/rand/index.test.ts +32 -0
  133. package/gs/crypto/rand/index.ts +90 -0
  134. package/gs/crypto/rand/meta.json +5 -0
  135. package/gs/encoding/json/index.test.ts +65 -0
  136. package/gs/encoding/json/index.ts +186 -0
  137. package/gs/fmt/fmt.test.ts +41 -30
  138. package/gs/fmt/fmt.ts +2 -22
  139. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  140. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  141. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  142. package/gs/go/scanner/index.test.ts +50 -0
  143. package/gs/go/scanner/index.ts +157 -0
  144. package/gs/go/token/index.test.ts +21 -0
  145. package/gs/go/token/index.ts +120 -0
  146. package/gs/os/file_unix_js.test.ts +103 -0
  147. package/gs/os/meta.json +1 -2
  148. package/gs/os/tempfile.gs.test.ts +85 -0
  149. package/gs/os/tempfile.gs.ts +71 -11
  150. package/gs/os/types_js.gs.ts +74 -153
  151. package/gs/reflect/index.ts +1 -1
  152. package/gs/reflect/type.ts +106 -17
  153. package/gs/reflect/typefor.test.ts +75 -0
  154. package/gs/sync/sync.test.ts +24 -0
  155. package/gs/sync/sync.ts +12 -0
  156. package/package.json +13 -13
  157. package/compiler/analysis.go +0 -3475
  158. package/compiler/analysis_test.go +0 -338
  159. package/compiler/assignment.go +0 -580
  160. package/compiler/builtin_test.go +0 -92
  161. package/compiler/code-writer.go +0 -115
  162. package/compiler/compiler_test.go +0 -149
  163. package/compiler/composite-lit.go +0 -779
  164. package/compiler/config_test.go +0 -62
  165. package/compiler/constraint.go +0 -86
  166. package/compiler/decl.go +0 -801
  167. package/compiler/expr-call-async.go +0 -188
  168. package/compiler/expr-call-builtins.go +0 -208
  169. package/compiler/expr-call-helpers.go +0 -382
  170. package/compiler/expr-call-make.go +0 -318
  171. package/compiler/expr-call-type-conversion.go +0 -520
  172. package/compiler/expr-call.go +0 -413
  173. package/compiler/expr-selector.go +0 -343
  174. package/compiler/expr-star.go +0 -82
  175. package/compiler/expr-type.go +0 -442
  176. package/compiler/expr-value.go +0 -89
  177. package/compiler/expr.go +0 -773
  178. package/compiler/field.go +0 -183
  179. package/compiler/gs_dependencies_test.go +0 -298
  180. package/compiler/lit.go +0 -322
  181. package/compiler/output.go +0 -72
  182. package/compiler/primitive.go +0 -149
  183. package/compiler/protobuf.go +0 -697
  184. package/compiler/sanitize.go +0 -100
  185. package/compiler/spec-struct.go +0 -995
  186. package/compiler/spec-value.go +0 -540
  187. package/compiler/spec.go +0 -725
  188. package/compiler/stmt-assign.go +0 -664
  189. package/compiler/stmt-for.go +0 -266
  190. package/compiler/stmt-range.go +0 -475
  191. package/compiler/stmt-select.go +0 -262
  192. package/compiler/stmt-type-switch.go +0 -147
  193. package/compiler/stmt.go +0 -1308
  194. package/compiler/type-assert.go +0 -386
  195. package/compiler/type-info.go +0 -156
  196. package/compiler/type-utils.go +0 -207
  197. 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
+ })
@@ -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(fieldType as TypeInfo | string),
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,5 @@
1
+ {
2
+ "dependencies": [
3
+ "io"
4
+ ]
5
+ }
@@ -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
+ }