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,413 @@
1
+ export class HostUnsupportedError extends Error {
2
+ constructor() {
3
+ super('operation not implemented in JavaScript environment')
4
+ }
5
+ }
6
+
7
+ export type NodeFSModule = {
8
+ readSync(
9
+ fd: number,
10
+ buffer: Uint8Array,
11
+ offset?: number,
12
+ length?: number,
13
+ position?: number | null,
14
+ ): number
15
+ writeSync(
16
+ fd: number,
17
+ buffer: Uint8Array,
18
+ offset?: number,
19
+ length?: number,
20
+ position?: number | null,
21
+ ): number
22
+ closeSync?(fd: number): void
23
+ fstatSync?(fd: number): any
24
+ fsyncSync?(fd: number): void
25
+ ftruncateSync?(fd: number, len?: number): void
26
+ openSync?(path: string, flags: number | string, mode?: number): number
27
+ chmodSync?(path: string, mode: number): void
28
+ chownSync?(path: string, uid: number, gid: number): void
29
+ lchownSync?(path: string, uid: number, gid: number): void
30
+ linkSync?(existingPath: string, newPath: string): void
31
+ lstatSync?(path: string): any
32
+ mkdirSync?(
33
+ path: string,
34
+ options?: number | { mode?: number; recursive?: boolean },
35
+ ): void
36
+ readFileSync?(path: string): Uint8Array
37
+ readdirSync?(path: string, options?: { withFileTypes?: boolean }): any[]
38
+ readlinkSync?(path: string): string
39
+ renameSync?(oldPath: string, newPath: string): void
40
+ rmSync?(
41
+ path: string,
42
+ options?: { force?: boolean; recursive?: boolean },
43
+ ): void
44
+ rmdirSync?(path: string): void
45
+ statSync?(path: string): any
46
+ symlinkSync?(target: string, path: string): void
47
+ truncateSync?(path: string, len?: number): void
48
+ unlinkSync?(path: string): void
49
+ utimesSync?(path: string, atime: Date | number, mtime: Date | number): void
50
+ writeFileSync?(
51
+ path: string,
52
+ data: Uint8Array,
53
+ options?: { mode?: number },
54
+ ): void
55
+ }
56
+
57
+ export type DenoStream = {
58
+ readSync?(buffer: Uint8Array): number | null
59
+ writeSync?(buffer: Uint8Array): number
60
+ }
61
+
62
+ export type DenoFileLike = DenoStream & {
63
+ close?(): void
64
+ rid?: number
65
+ seekSync?(offset: number, whence: number): number
66
+ syncSync?(): void
67
+ statSync?(): any
68
+ truncateSync?(len?: number): void
69
+ }
70
+
71
+ type HostReadFD = (fd: number, buffer: Uint8Array) => number | null
72
+ type HostWriteFD = (fd: number, buffer: Uint8Array) => number
73
+ type HostTextWrite = (data: string) => void
74
+
75
+ export type HostRuntime = {
76
+ deno: any | null
77
+ nodeFS: NodeFSModule | null
78
+ platform: string
79
+ processObj: any | null
80
+ getEnv(name: string): string
81
+ getStdioHandle(fd: number): DenoFileLike | null
82
+ readFD: HostReadFD
83
+ writeFD: HostWriteFD
84
+ writeStderrText: HostTextWrite
85
+ writeStdoutText: HostTextWrite
86
+ }
87
+
88
+ export type HostRuntimeDetector = () => HostRuntime
89
+
90
+ export type MainScriptMeta = {
91
+ url: string
92
+ main?: boolean
93
+ }
94
+
95
+ const encoder = new TextEncoder()
96
+
97
+ function getDynamicRequire(): ((specifier: string) => unknown) | null {
98
+ try {
99
+ return Function(
100
+ "return typeof require !== 'undefined' ? require : null",
101
+ )() as ((specifier: string) => unknown) | null
102
+ } catch {
103
+ return null
104
+ }
105
+ }
106
+
107
+ function writeAllSync(
108
+ writeChunk: (chunk: Uint8Array) => number,
109
+ buffer: Uint8Array,
110
+ ): number {
111
+ let offset = 0
112
+ while (offset < buffer.length) {
113
+ const n = writeChunk(buffer.subarray(offset))
114
+ if (!Number.isFinite(n) || n < 0) {
115
+ throw new Error(`invalid write result: ${n}`)
116
+ }
117
+ if (n === 0) {
118
+ throw new Error('short write')
119
+ }
120
+ offset += n
121
+ }
122
+ return buffer.length
123
+ }
124
+
125
+ function writeAllText(
126
+ writeChunk: (chunk: Uint8Array) => number,
127
+ data: string,
128
+ ): void {
129
+ const bytes = encoder.encode(data)
130
+ if (bytes.length === 0) {
131
+ return
132
+ }
133
+ writeAllSync(writeChunk, bytes)
134
+ }
135
+
136
+ function detectNodeFS(processObj: any | null): NodeFSModule | null {
137
+ if (processObj && typeof processObj.getBuiltinModule === 'function') {
138
+ const module = processObj.getBuiltinModule('fs')
139
+ if (
140
+ module &&
141
+ typeof module.readSync === 'function' &&
142
+ typeof module.writeSync === 'function'
143
+ ) {
144
+ return module as NodeFSModule
145
+ }
146
+ }
147
+
148
+ const requireFn = getDynamicRequire()
149
+ if (requireFn) {
150
+ for (const specifier of ['node:fs', 'fs']) {
151
+ try {
152
+ const module = requireFn(specifier) as NodeFSModule | null
153
+ if (
154
+ module &&
155
+ typeof module.readSync === 'function' &&
156
+ typeof module.writeSync === 'function'
157
+ ) {
158
+ return module
159
+ }
160
+ } catch {
161
+ // Try the next fallback.
162
+ }
163
+ }
164
+ }
165
+
166
+ return null
167
+ }
168
+
169
+ function hasURLScheme(path: string): boolean {
170
+ return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(path)
171
+ }
172
+
173
+ function isAbsoluteScriptPath(path: string): boolean {
174
+ return path.startsWith('/') || /^[A-Za-z]:[\\/]/.test(path)
175
+ }
176
+
177
+ function normalizePath(path: string): string {
178
+ return path.replace(/\\/g, '/')
179
+ }
180
+
181
+ function absolutePathToFileURL(path: string, isDirectory: boolean): string {
182
+ const normalized = normalizePath(path)
183
+ const suffix = isDirectory && !normalized.endsWith('/') ? '/' : ''
184
+ if (/^[A-Za-z]:\//.test(normalized)) {
185
+ return new URL(`file:///${normalized}${suffix}`).href
186
+ }
187
+ return new URL(`file://${normalized}${suffix}`).href
188
+ }
189
+
190
+ function getCurrentWorkingDirectory(): string | null {
191
+ const runtime = getHostRuntime()
192
+ try {
193
+ if (typeof runtime.processObj?.cwd === 'function') {
194
+ return runtime.processObj.cwd()
195
+ }
196
+ } catch {
197
+ // Fall through to Deno cwd.
198
+ }
199
+
200
+ try {
201
+ if (typeof runtime.deno?.cwd === 'function') {
202
+ return runtime.deno.cwd()
203
+ }
204
+ } catch {
205
+ // No cwd fallback available.
206
+ }
207
+
208
+ return null
209
+ }
210
+
211
+ function fileURLFromScriptPath(path: string): string | null {
212
+ try {
213
+ if (hasURLScheme(path)) {
214
+ return new URL(path).href
215
+ }
216
+
217
+ if (isAbsoluteScriptPath(path)) {
218
+ return absolutePathToFileURL(path, false)
219
+ }
220
+
221
+ const cwd = getCurrentWorkingDirectory()
222
+ if (!cwd) {
223
+ return null
224
+ }
225
+ return new URL(normalizePath(path), absolutePathToFileURL(cwd, true)).href
226
+ } catch {
227
+ return null
228
+ }
229
+ }
230
+
231
+ function fallbackConsoleWriter(method: 'error' | 'log'): HostTextWrite {
232
+ return (data: string) => {
233
+ const consoleMethod = (globalThis as any).console?.[method]
234
+ if (!consoleMethod) {
235
+ return
236
+ }
237
+ if (data.endsWith('\n')) {
238
+ consoleMethod.call((globalThis as any).console, data.slice(0, -1))
239
+ return
240
+ }
241
+ consoleMethod.call((globalThis as any).console, data)
242
+ }
243
+ }
244
+
245
+ function detectHostRuntime(): HostRuntime {
246
+ const globalObj = globalThis as any
247
+ const deno = globalObj.Deno ?? null
248
+ const processObj = globalObj.process ?? null
249
+ const nodeFS = detectNodeFS(processObj)
250
+
251
+ const getStdioHandle = (fd: number): DenoFileLike | null => {
252
+ if (!deno) {
253
+ return null
254
+ }
255
+ switch (fd) {
256
+ case 0:
257
+ return deno.stdin ?? null
258
+ case 1:
259
+ return deno.stdout ?? null
260
+ case 2:
261
+ return deno.stderr ?? null
262
+ default:
263
+ return null
264
+ }
265
+ }
266
+
267
+ const platform = deno?.build?.os ?? processObj?.platform ?? 'unknown'
268
+
269
+ const getEnv = (name: string): string => {
270
+ if (deno?.env?.get) {
271
+ try {
272
+ return deno.env.get(name) ?? ''
273
+ } catch {
274
+ return ''
275
+ }
276
+ }
277
+ return processObj?.env?.[name] ?? ''
278
+ }
279
+
280
+ const readFD: HostReadFD = (
281
+ fd: number,
282
+ buffer: Uint8Array,
283
+ ): number | null => {
284
+ const handle = getStdioHandle(fd)
285
+ if (handle && typeof handle.readSync === 'function') {
286
+ return handle.readSync(buffer)
287
+ }
288
+ if (nodeFS) {
289
+ return nodeFS.readSync(fd, buffer, 0, buffer.length, null)
290
+ }
291
+ throw new HostUnsupportedError()
292
+ }
293
+ const writeFD: HostWriteFD = (fd: number, buffer: Uint8Array): number => {
294
+ const handle = getStdioHandle(fd)
295
+ if (handle && typeof handle.writeSync === 'function') {
296
+ return writeAllSync(
297
+ (chunk: Uint8Array) => handle.writeSync!(chunk),
298
+ buffer,
299
+ )
300
+ }
301
+ if (nodeFS) {
302
+ return writeAllSync(
303
+ (chunk: Uint8Array) =>
304
+ nodeFS.writeSync(fd, chunk, 0, chunk.length, null),
305
+ buffer,
306
+ )
307
+ }
308
+ throw new HostUnsupportedError()
309
+ }
310
+ const writeStdoutText: HostTextWrite = (data: string) => {
311
+ const handle = getStdioHandle(1)
312
+ if (handle && typeof handle.writeSync === 'function') {
313
+ writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
314
+ return
315
+ }
316
+ if (nodeFS) {
317
+ writeAllText(
318
+ (chunk: Uint8Array) =>
319
+ nodeFS.writeSync(1, chunk, 0, chunk.length, null),
320
+ data,
321
+ )
322
+ return
323
+ }
324
+ fallbackConsoleWriter('log')(data)
325
+ }
326
+ const writeStderrText: HostTextWrite = (data: string) => {
327
+ const handle = getStdioHandle(2)
328
+ if (handle && typeof handle.writeSync === 'function') {
329
+ writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
330
+ return
331
+ }
332
+ if (nodeFS) {
333
+ writeAllText(
334
+ (chunk: Uint8Array) =>
335
+ nodeFS.writeSync(2, chunk, 0, chunk.length, null),
336
+ data,
337
+ )
338
+ return
339
+ }
340
+ fallbackConsoleWriter('error')(data)
341
+ }
342
+
343
+ return {
344
+ deno,
345
+ getEnv,
346
+ getStdioHandle,
347
+ nodeFS,
348
+ platform,
349
+ processObj,
350
+ readFD,
351
+ writeFD,
352
+ writeStderrText,
353
+ writeStdoutText,
354
+ }
355
+ }
356
+
357
+ export class HostRuntimeOwner {
358
+ private runtime: HostRuntime
359
+
360
+ constructor(
361
+ private readonly detector: HostRuntimeDetector = detectHostRuntime,
362
+ ) {
363
+ this.runtime = detector()
364
+ }
365
+
366
+ current(): HostRuntime {
367
+ return this.runtime
368
+ }
369
+
370
+ reset(): void {
371
+ this.runtime = this.detector()
372
+ }
373
+ }
374
+
375
+ export const hostRuntimeOwner = new HostRuntimeOwner()
376
+
377
+ export function getHostRuntime(): HostRuntime {
378
+ return hostRuntimeOwner.current()
379
+ }
380
+
381
+ export function resetHostRuntimeForTests(): void {
382
+ hostRuntimeOwner.reset()
383
+ }
384
+
385
+ export function writeHostStdoutText(data: string): void {
386
+ getHostRuntime().writeStdoutText(data)
387
+ }
388
+
389
+ export function writeHostStderrText(data: string): void {
390
+ getHostRuntime().writeStderrText(data)
391
+ }
392
+
393
+ export function isMainScript(meta: MainScriptMeta): boolean {
394
+ if (meta.main === true) {
395
+ return true
396
+ }
397
+
398
+ const entryPath = getHostRuntime().processObj?.argv?.[1]
399
+ if (typeof entryPath !== 'string' || entryPath === '') {
400
+ return false
401
+ }
402
+
403
+ const entryURL = fileURLFromScriptPath(entryPath)
404
+ if (!entryURL) {
405
+ return false
406
+ }
407
+
408
+ try {
409
+ return new URL(meta.url).href === entryURL
410
+ } catch {
411
+ return meta.url === entryURL
412
+ }
413
+ }
@@ -6,3 +6,4 @@ export * from './type.js'
6
6
  export * from './varRef.js'
7
7
  export * from './defer.js'
8
8
  export * from './errors.js'
9
+ export * from './hostio.js'
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { formatPrintedArgs } from './print.js'
4
+
5
+ describe('builtin println formatting', () => {
6
+ it('formats Uint8Array values with a stable inspect-style representation', () => {
7
+ expect(
8
+ formatPrintedArgs(['b2:', new Uint8Array([72, 101, 108, 108, 111])]),
9
+ ).toBe('b2: Uint8Array(5) [ 72, 101, 108, 108, 111 ]')
10
+ })
11
+
12
+ it('quotes nested string array elements', () => {
13
+ expect(formatPrintedArgs(['strings.Split:', ['a', 'b', 'c']])).toBe(
14
+ 'strings.Split: [ "a", "b", "c" ]',
15
+ )
16
+ })
17
+
18
+ it('formats plain objects across multiple lines', () => {
19
+ expect(
20
+ formatPrintedArgs([
21
+ 'out:',
22
+ {
23
+ exampleField: new Uint8Array([104, 101, 108, 108, 111]),
24
+ exampleText: 'world',
25
+ },
26
+ ]),
27
+ ).toBe(`out: {
28
+ exampleField: Uint8Array(5) [ 104, 101, 108, 108, 111 ],
29
+ exampleText: "world",
30
+ }`)
31
+ })
32
+
33
+ it('formats goscript struct field bags using field values', () => {
34
+ expect(
35
+ formatPrintedArgs([
36
+ {
37
+ _fields: {
38
+ Name: { value: 'hello' },
39
+ Count: { value: 3 },
40
+ },
41
+ },
42
+ ]),
43
+ ).toBe(`{
44
+ Name: "hello",
45
+ Count: 3,
46
+ }`)
47
+ })
48
+ })
@@ -0,0 +1,154 @@
1
+ import { asArray, isSliceProxy, type Slice } from './slice.js'
2
+
3
+ /**
4
+ * formatPrintedArgs formats builtin println arguments deterministically.
5
+ */
6
+ export function formatPrintedArgs(args: readonly any[]): string {
7
+ return args.map((arg) => formatPrintedValue(arg)).join(' ')
8
+ }
9
+
10
+ /**
11
+ * formatPrintedValue formats a single builtin println argument deterministically.
12
+ */
13
+ export function formatPrintedValue(value: any): string {
14
+ return formatValue(value, 0, false, new WeakSet<object>())
15
+ }
16
+
17
+ function formatValue(
18
+ value: any,
19
+ depth: number,
20
+ nested: boolean,
21
+ seen: WeakSet<object>,
22
+ ): string {
23
+ if (value === null) {
24
+ return 'null'
25
+ }
26
+
27
+ if (value === undefined) {
28
+ return '<nil>'
29
+ }
30
+
31
+ if (typeof value === 'string') {
32
+ return nested ? JSON.stringify(value) : value
33
+ }
34
+
35
+ if (typeof value === 'boolean' || typeof value === 'number') {
36
+ return String(value)
37
+ }
38
+
39
+ if (typeof value === 'bigint') {
40
+ return value.toString()
41
+ }
42
+
43
+ if (value instanceof Uint8Array) {
44
+ return formatUint8Array(value)
45
+ }
46
+
47
+ if (Array.isArray(value)) {
48
+ return formatArray(value, depth, seen)
49
+ }
50
+
51
+ if (isSliceProxy(value as Slice<unknown>)) {
52
+ return formatArray(asArray(value as Slice<unknown>), depth, seen)
53
+ }
54
+
55
+ if (typeof value === 'function') {
56
+ return value.name ? `[Function: ${value.name}]` : '[Function]'
57
+ }
58
+
59
+ if (typeof value !== 'object') {
60
+ return String(value)
61
+ }
62
+
63
+ if (seen.has(value)) {
64
+ return '[Circular]'
65
+ }
66
+ seen.add(value)
67
+
68
+ try {
69
+ if (value instanceof Map) {
70
+ return formatArray(
71
+ Array.from(value.entries()).map(([k, v]) => `${formatValue(k, depth + 1, true, seen)} => ${formatValue(v, depth + 1, true, seen)}`),
72
+ depth,
73
+ seen,
74
+ )
75
+ }
76
+
77
+ if (value instanceof Set) {
78
+ return formatArray(Array.from(value.values()), depth, seen)
79
+ }
80
+
81
+ if (value instanceof Error) {
82
+ return value.message || value.toString()
83
+ }
84
+
85
+ if (typeof value.GoString === 'function') {
86
+ return value.GoString()
87
+ }
88
+
89
+ if (typeof value.Error === 'function') {
90
+ return value.Error()
91
+ }
92
+
93
+ if (typeof value.String === 'function') {
94
+ return value.String()
95
+ }
96
+
97
+ const entries = getObjectEntries(value)
98
+ if (entries.length === 0) {
99
+ return '{}'
100
+ }
101
+
102
+ return formatObject(entries, depth, seen)
103
+ } finally {
104
+ seen.delete(value)
105
+ }
106
+ }
107
+
108
+ function formatUint8Array(value: Uint8Array): string {
109
+ if (value.length === 0) {
110
+ return 'Uint8Array(0) []'
111
+ }
112
+
113
+ return `Uint8Array(${value.length}) [ ${Array.from(value).join(', ')} ]`
114
+ }
115
+
116
+ function formatArray(value: readonly any[], depth: number, seen: WeakSet<object>): string {
117
+ if (value.length === 0) {
118
+ return '[]'
119
+ }
120
+
121
+ return `[ ${value.map((item) => formatValue(item, depth + 1, true, seen)).join(', ')} ]`
122
+ }
123
+
124
+ function formatObject(
125
+ entries: readonly [string, any][],
126
+ depth: number,
127
+ seen: WeakSet<object>,
128
+ ): string {
129
+ const pad = ' '.repeat(depth + 1)
130
+ const closePad = ' '.repeat(depth)
131
+ const body = entries
132
+ .map(
133
+ ([key, value]) =>
134
+ `${pad}${key}: ${formatValue(value, depth + 1, true, seen)},`,
135
+ )
136
+ .join('\n')
137
+
138
+ return `{\n${body}\n${closePad}}`
139
+ }
140
+
141
+ function getObjectEntries(value: Record<string, any>): [string, any][] {
142
+ const fields = value._fields
143
+ if (fields && typeof fields === 'object' && !Array.isArray(fields)) {
144
+ return Object.keys(fields).map((key) => {
145
+ const field = fields[key]
146
+ if (field && typeof field === 'object' && 'value' in field) {
147
+ return [key, field.value]
148
+ }
149
+ return [key, field]
150
+ })
151
+ }
152
+
153
+ return Object.entries(value).filter(([, entry]) => typeof entry !== 'function')
154
+ }