goscript 0.0.83 → 0.0.84
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/dist/gs/builtin/builtin.js +4 -7
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/hostio.d.ts +72 -0
- package/dist/gs/builtin/hostio.js +181 -0
- package/dist/gs/builtin/hostio.js.map +1 -0
- 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/fmt/fmt.js +2 -22
- package/dist/gs/fmt/fmt.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/gs/builtin/builtin.ts +4 -6
- package/gs/builtin/hostio.test.ts +69 -0
- package/gs/builtin/hostio.ts +298 -0
- package/gs/builtin/print.test.ts +48 -0
- package/gs/builtin/print.ts +154 -0
- package/gs/fmt/fmt.test.ts +41 -30
- package/gs/fmt/fmt.ts +2 -22
- package/gs/os/file_unix_js.test.ts +53 -0
- package/gs/os/types_js.gs.ts +74 -153
- package/package.json +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
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?(path: string, options?: number | { mode?: number; recursive?: boolean }): void
|
|
33
|
+
readFileSync?(path: string): Uint8Array
|
|
34
|
+
readdirSync?(path: string, options?: { withFileTypes?: boolean }): any[]
|
|
35
|
+
readlinkSync?(path: string): string
|
|
36
|
+
renameSync?(oldPath: string, newPath: string): void
|
|
37
|
+
rmSync?(path: string, options?: { force?: boolean; recursive?: boolean }): void
|
|
38
|
+
rmdirSync?(path: string): void
|
|
39
|
+
statSync?(path: string): any
|
|
40
|
+
symlinkSync?(target: string, path: string): void
|
|
41
|
+
truncateSync?(path: string, len?: number): void
|
|
42
|
+
unlinkSync?(path: string): void
|
|
43
|
+
utimesSync?(path: string, atime: Date | number, mtime: Date | number): void
|
|
44
|
+
writeFileSync?(path: string, data: Uint8Array, options?: { mode?: number }): void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type DenoStream = {
|
|
48
|
+
readSync?(buffer: Uint8Array): number | null
|
|
49
|
+
writeSync?(buffer: Uint8Array): number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type DenoFileLike = DenoStream & {
|
|
53
|
+
close?(): void
|
|
54
|
+
rid?: number
|
|
55
|
+
seekSync?(offset: number, whence: number): number
|
|
56
|
+
syncSync?(): void
|
|
57
|
+
statSync?(): any
|
|
58
|
+
truncateSync?(len?: number): void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type HostReadFD = (fd: number, buffer: Uint8Array) => number | null
|
|
62
|
+
type HostWriteFD = (fd: number, buffer: Uint8Array) => number
|
|
63
|
+
type HostTextWrite = (data: string) => void
|
|
64
|
+
|
|
65
|
+
export type HostRuntime = {
|
|
66
|
+
deno: any | null
|
|
67
|
+
nodeFS: NodeFSModule | null
|
|
68
|
+
platform: string
|
|
69
|
+
processObj: any | null
|
|
70
|
+
getEnv(name: string): string
|
|
71
|
+
getStdioHandle(fd: number): DenoFileLike | null
|
|
72
|
+
readFD: HostReadFD
|
|
73
|
+
writeFD: HostWriteFD
|
|
74
|
+
writeStderrText: HostTextWrite
|
|
75
|
+
writeStdoutText: HostTextWrite
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const encoder = new TextEncoder()
|
|
79
|
+
|
|
80
|
+
function getDynamicRequire(): ((specifier: string) => unknown) | null {
|
|
81
|
+
try {
|
|
82
|
+
return Function(
|
|
83
|
+
"return typeof require !== 'undefined' ? require : null",
|
|
84
|
+
)() as ((specifier: string) => unknown) | null
|
|
85
|
+
} catch {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeAllSync(
|
|
91
|
+
writeChunk: (chunk: Uint8Array) => number,
|
|
92
|
+
buffer: Uint8Array,
|
|
93
|
+
): number {
|
|
94
|
+
let offset = 0
|
|
95
|
+
while (offset < buffer.length) {
|
|
96
|
+
const n = writeChunk(buffer.subarray(offset))
|
|
97
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
98
|
+
throw new Error(`invalid write result: ${n}`)
|
|
99
|
+
}
|
|
100
|
+
if (n === 0) {
|
|
101
|
+
throw new Error('short write')
|
|
102
|
+
}
|
|
103
|
+
offset += n
|
|
104
|
+
}
|
|
105
|
+
return buffer.length
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeAllText(
|
|
109
|
+
writeChunk: (chunk: Uint8Array) => number,
|
|
110
|
+
data: string,
|
|
111
|
+
): void {
|
|
112
|
+
const bytes = encoder.encode(data)
|
|
113
|
+
if (bytes.length === 0) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
writeAllSync(writeChunk, bytes)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function detectNodeFS(processObj: any | null): NodeFSModule | null {
|
|
120
|
+
if (processObj && typeof processObj.getBuiltinModule === 'function') {
|
|
121
|
+
const module = processObj.getBuiltinModule('fs')
|
|
122
|
+
if (
|
|
123
|
+
module &&
|
|
124
|
+
typeof module.readSync === 'function' &&
|
|
125
|
+
typeof module.writeSync === 'function'
|
|
126
|
+
) {
|
|
127
|
+
return module as NodeFSModule
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const requireFn = getDynamicRequire()
|
|
132
|
+
if (requireFn) {
|
|
133
|
+
for (const specifier of ['node:fs', 'fs']) {
|
|
134
|
+
try {
|
|
135
|
+
const module = requireFn(specifier) as NodeFSModule | null
|
|
136
|
+
if (
|
|
137
|
+
module &&
|
|
138
|
+
typeof module.readSync === 'function' &&
|
|
139
|
+
typeof module.writeSync === 'function'
|
|
140
|
+
) {
|
|
141
|
+
return module
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Try the next fallback.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function unsupportedReadFD(_fd: number, _buffer: Uint8Array): number | null {
|
|
153
|
+
throw new HostUnsupportedError()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function unsupportedWriteFD(_fd: number, _buffer: Uint8Array): number {
|
|
157
|
+
throw new HostUnsupportedError()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function fallbackConsoleWriter(method: 'error' | 'log'): HostTextWrite {
|
|
161
|
+
return (data: string) => {
|
|
162
|
+
const consoleMethod = (globalThis as any).console?.[method]
|
|
163
|
+
if (!consoleMethod) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
if (data.endsWith('\n')) {
|
|
167
|
+
consoleMethod.call((globalThis as any).console, data.slice(0, -1))
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
consoleMethod.call((globalThis as any).console, data)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function detectHostRuntime(): HostRuntime {
|
|
175
|
+
const globalObj = globalThis as any
|
|
176
|
+
const deno = globalObj.Deno ?? null
|
|
177
|
+
const processObj = globalObj.process ?? null
|
|
178
|
+
const nodeFS = detectNodeFS(processObj)
|
|
179
|
+
|
|
180
|
+
const getStdioHandle = (fd: number): DenoFileLike | null => {
|
|
181
|
+
if (!deno) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
switch (fd) {
|
|
185
|
+
case 0:
|
|
186
|
+
return deno.stdin ?? null
|
|
187
|
+
case 1:
|
|
188
|
+
return deno.stdout ?? null
|
|
189
|
+
case 2:
|
|
190
|
+
return deno.stderr ?? null
|
|
191
|
+
default:
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const platform =
|
|
197
|
+
deno?.build?.os ??
|
|
198
|
+
processObj?.platform ??
|
|
199
|
+
'unknown'
|
|
200
|
+
|
|
201
|
+
const getEnv = (name: string): string => {
|
|
202
|
+
if (deno?.env?.get) {
|
|
203
|
+
try {
|
|
204
|
+
return deno.env.get(name) ?? ''
|
|
205
|
+
} catch {
|
|
206
|
+
return ''
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return processObj?.env?.[name] ?? ''
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let readFD: HostReadFD = unsupportedReadFD
|
|
213
|
+
let writeFD: HostWriteFD = unsupportedWriteFD
|
|
214
|
+
let writeStdoutText: HostTextWrite = fallbackConsoleWriter('log')
|
|
215
|
+
let writeStderrText: HostTextWrite = fallbackConsoleWriter('error')
|
|
216
|
+
|
|
217
|
+
if (deno) {
|
|
218
|
+
readFD = (fd: number, buffer: Uint8Array): number | null => {
|
|
219
|
+
const handle = getStdioHandle(fd)
|
|
220
|
+
if (!handle || typeof handle.readSync !== 'function') {
|
|
221
|
+
throw new HostUnsupportedError()
|
|
222
|
+
}
|
|
223
|
+
return handle.readSync(buffer)
|
|
224
|
+
}
|
|
225
|
+
writeFD = (fd: number, buffer: Uint8Array): number => {
|
|
226
|
+
const handle = getStdioHandle(fd)
|
|
227
|
+
if (!handle || typeof handle.writeSync !== 'function') {
|
|
228
|
+
throw new HostUnsupportedError()
|
|
229
|
+
}
|
|
230
|
+
return writeAllSync(
|
|
231
|
+
(chunk: Uint8Array) => handle.writeSync!(chunk),
|
|
232
|
+
buffer,
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
writeStdoutText = (data: string) => {
|
|
236
|
+
const handle = getStdioHandle(1)
|
|
237
|
+
if (!handle || typeof handle.writeSync !== 'function') {
|
|
238
|
+
fallbackConsoleWriter('log')(data)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
|
|
242
|
+
}
|
|
243
|
+
writeStderrText = (data: string) => {
|
|
244
|
+
const handle = getStdioHandle(2)
|
|
245
|
+
if (!handle || typeof handle.writeSync !== 'function') {
|
|
246
|
+
fallbackConsoleWriter('error')(data)
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
|
|
250
|
+
}
|
|
251
|
+
} else if (nodeFS) {
|
|
252
|
+
readFD = (fd: number, buffer: Uint8Array): number | null =>
|
|
253
|
+
nodeFS.readSync(fd, buffer, 0, buffer.length, null)
|
|
254
|
+
writeFD = (fd: number, buffer: Uint8Array): number =>
|
|
255
|
+
writeAllSync(
|
|
256
|
+
(chunk: Uint8Array) =>
|
|
257
|
+
nodeFS.writeSync(fd, chunk, 0, chunk.length, null),
|
|
258
|
+
buffer,
|
|
259
|
+
)
|
|
260
|
+
writeStdoutText = (data: string) =>
|
|
261
|
+
writeAllText(
|
|
262
|
+
(chunk: Uint8Array) => nodeFS.writeSync(1, chunk, 0, chunk.length, null),
|
|
263
|
+
data,
|
|
264
|
+
)
|
|
265
|
+
writeStderrText = (data: string) =>
|
|
266
|
+
writeAllText(
|
|
267
|
+
(chunk: Uint8Array) => nodeFS.writeSync(2, chunk, 0, chunk.length, null),
|
|
268
|
+
data,
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
deno,
|
|
274
|
+
getEnv,
|
|
275
|
+
getStdioHandle,
|
|
276
|
+
nodeFS,
|
|
277
|
+
platform,
|
|
278
|
+
processObj,
|
|
279
|
+
readFD,
|
|
280
|
+
writeFD,
|
|
281
|
+
writeStderrText,
|
|
282
|
+
writeStdoutText,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export let hostRuntime = detectHostRuntime()
|
|
287
|
+
|
|
288
|
+
export function resetHostRuntimeForTests(): void {
|
|
289
|
+
hostRuntime = detectHostRuntime()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function writeHostStdoutText(data: string): void {
|
|
293
|
+
hostRuntime.writeStdoutText(data)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function writeHostStderrText(data: string): void {
|
|
297
|
+
hostRuntime.writeStderrText(data)
|
|
298
|
+
}
|
|
@@ -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
|
+
}
|
package/gs/fmt/fmt.test.ts
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
1
|
-
import { describe, it,
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { resetHostRuntimeForTests } from '@goscript/builtin/hostio.js'
|
|
2
3
|
import * as fmt from './fmt.js'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const originalDeno = (globalThis as any).Deno
|
|
6
|
+
const originalProcess = (globalThis as any).process
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
if (originalDeno === undefined) {
|
|
10
|
+
delete (globalThis as any).Deno
|
|
11
|
+
} else {
|
|
12
|
+
;(globalThis as any).Deno = originalDeno
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (originalProcess === undefined) {
|
|
16
|
+
delete (globalThis as any).process
|
|
17
|
+
} else {
|
|
18
|
+
;(globalThis as any).process = originalProcess
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
resetHostRuntimeForTests()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Helper to capture stdout via the hostio text output path.
|
|
6
25
|
function captureStdout(run: () => void): string {
|
|
7
26
|
let buf = ''
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Fallback: spy on console.log for environments without process
|
|
26
|
-
const origLog = console.log
|
|
27
|
-
;(console as any).log = (msg: any) => {
|
|
28
|
-
buf += String(msg) + '\n'
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
run()
|
|
32
|
-
} finally {
|
|
33
|
-
console.log = origLog
|
|
34
|
-
}
|
|
27
|
+
delete (globalThis as any).Deno
|
|
28
|
+
;(globalThis as any).process = {
|
|
29
|
+
getBuiltinModule: vi.fn(() => ({
|
|
30
|
+
readSync: vi.fn(),
|
|
31
|
+
writeSync: vi.fn(
|
|
32
|
+
(
|
|
33
|
+
_fd: number,
|
|
34
|
+
chunk: Uint8Array,
|
|
35
|
+
_offset?: number,
|
|
36
|
+
length?: number,
|
|
37
|
+
_position?: number | null,
|
|
38
|
+
) => {
|
|
39
|
+
buf += new TextDecoder().decode(chunk.subarray(0, length ?? chunk.length))
|
|
40
|
+
return length ?? chunk.length
|
|
41
|
+
},
|
|
42
|
+
),
|
|
43
|
+
})),
|
|
35
44
|
}
|
|
45
|
+
resetHostRuntimeForTests()
|
|
46
|
+
run()
|
|
36
47
|
|
|
37
48
|
return buf
|
|
38
49
|
}
|
package/gs/fmt/fmt.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import * as $ from '@goscript/builtin/index.js'
|
|
5
5
|
import * as errors from '@goscript/errors/index.js'
|
|
6
|
+
import { writeHostStdoutText } from '@goscript/builtin/hostio.js'
|
|
6
7
|
|
|
7
8
|
// Basic interfaces
|
|
8
9
|
export interface Stringer {
|
|
@@ -259,28 +260,7 @@ function parseFormat(format: string, args: any[]): string {
|
|
|
259
260
|
// Global stdout simulation for Print functions
|
|
260
261
|
let stdout = {
|
|
261
262
|
write: (data: string) => {
|
|
262
|
-
|
|
263
|
-
// but we need to avoid adding extra newlines that console.log adds
|
|
264
|
-
if (
|
|
265
|
-
typeof process !== 'undefined' &&
|
|
266
|
-
process.stdout &&
|
|
267
|
-
process.stdout.write
|
|
268
|
-
) {
|
|
269
|
-
process.stdout.write(data)
|
|
270
|
-
} else {
|
|
271
|
-
// In browser environments, we need to use console.log but handle newlines carefully
|
|
272
|
-
// If the data already ends with \n, we should strip it to avoid double newlines
|
|
273
|
-
if (data.endsWith('\n')) {
|
|
274
|
-
console.log(data.slice(0, -1))
|
|
275
|
-
} else {
|
|
276
|
-
// Use console.log without adding newline by using a custom method
|
|
277
|
-
if (console.log) {
|
|
278
|
-
// For data without newlines, we can just print it directly
|
|
279
|
-
// This is a bit of a hack but works for most cases
|
|
280
|
-
console.log(data)
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
263
|
+
writeHostStdoutText(data)
|
|
284
264
|
},
|
|
285
265
|
}
|
|
286
266
|
|
|
@@ -4,6 +4,7 @@ import * as io from '@goscript/io/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { NewFile, Stderr, Stdin, Stdout } from './file_unix_js.gs.js'
|
|
6
6
|
import { ErrClosed } from './error.gs.js'
|
|
7
|
+
import { createHostFile, resetHostRuntimeForTests } from './types_js.gs.js'
|
|
7
8
|
|
|
8
9
|
const originalDeno = (globalThis as any).Deno
|
|
9
10
|
const originalProcess = (globalThis as any).process
|
|
@@ -20,6 +21,8 @@ afterEach(() => {
|
|
|
20
21
|
} else {
|
|
21
22
|
;(globalThis as any).process = originalProcess
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
resetHostRuntimeForTests()
|
|
23
26
|
})
|
|
24
27
|
|
|
25
28
|
describe('os stdio', () => {
|
|
@@ -45,6 +48,7 @@ describe('os stdio', () => {
|
|
|
45
48
|
stdout: { writeSync: stdoutWriteSync },
|
|
46
49
|
}
|
|
47
50
|
delete (globalThis as any).process
|
|
51
|
+
resetHostRuntimeForTests()
|
|
48
52
|
|
|
49
53
|
const input = NewFile(0, 'stdin')
|
|
50
54
|
const output = NewFile(1, 'stdout')
|
|
@@ -93,6 +97,7 @@ describe('os stdio', () => {
|
|
|
93
97
|
writeSync,
|
|
94
98
|
})),
|
|
95
99
|
}
|
|
100
|
+
resetHostRuntimeForTests()
|
|
96
101
|
|
|
97
102
|
const input = NewFile(0, 'stdin')
|
|
98
103
|
const output = NewFile(1, 'stdout')
|
|
@@ -117,6 +122,7 @@ describe('os stdio', () => {
|
|
|
117
122
|
stdin: { readSync: stdinReadSync },
|
|
118
123
|
}
|
|
119
124
|
delete (globalThis as any).process
|
|
125
|
+
resetHostRuntimeForTests()
|
|
120
126
|
|
|
121
127
|
const input = NewFile(0, 'stdin')!
|
|
122
128
|
const [readN, readErr] = input.Read(new Uint8Array(1))
|
|
@@ -128,4 +134,51 @@ describe('os stdio', () => {
|
|
|
128
134
|
expect(closedN).toBe(0)
|
|
129
135
|
expect(closedErr).toBe(ErrClosed)
|
|
130
136
|
})
|
|
137
|
+
|
|
138
|
+
it('retries short writes until the full buffer is written', () => {
|
|
139
|
+
const writeSync = vi
|
|
140
|
+
.fn<(buffer: Uint8Array) => number>()
|
|
141
|
+
.mockImplementationOnce((_buffer: Uint8Array) => 2)
|
|
142
|
+
.mockImplementationOnce((buffer: Uint8Array) => buffer.length)
|
|
143
|
+
|
|
144
|
+
;(globalThis as any).Deno = {
|
|
145
|
+
stdout: { writeSync },
|
|
146
|
+
}
|
|
147
|
+
delete (globalThis as any).process
|
|
148
|
+
resetHostRuntimeForTests()
|
|
149
|
+
|
|
150
|
+
const output = NewFile(1, 'stdout')!
|
|
151
|
+
const [writeN, writeErr] = output.Write(new Uint8Array([1, 2, 3, 4, 5]))
|
|
152
|
+
expect(writeN).toBe(5)
|
|
153
|
+
expect(writeErr).toBeNull()
|
|
154
|
+
expect(writeSync).toHaveBeenCalledTimes(2)
|
|
155
|
+
expect(Array.from(writeSync.mock.calls[1][0])).toEqual([3, 4, 5])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('prefers direct handle read and write methods when available', () => {
|
|
159
|
+
const handle = {
|
|
160
|
+
readSync: vi.fn((buffer: Uint8Array) => {
|
|
161
|
+
buffer.set([11, 12, 13], 0)
|
|
162
|
+
return 3
|
|
163
|
+
}),
|
|
164
|
+
writeSync: vi
|
|
165
|
+
.fn<(buffer: Uint8Array) => number>()
|
|
166
|
+
.mockImplementationOnce((_buffer: Uint8Array) => 1)
|
|
167
|
+
.mockImplementationOnce((buffer: Uint8Array) => buffer.length),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const file = createHostFile('host-file', 99, handle)
|
|
171
|
+
const readBuf = new Uint8Array(4)
|
|
172
|
+
|
|
173
|
+
const [readN, readErr] = file.Read(readBuf)
|
|
174
|
+
expect(readN).toBe(3)
|
|
175
|
+
expect(readErr).toBeNull()
|
|
176
|
+
expect(Array.from(readBuf.slice(0, 3))).toEqual([11, 12, 13])
|
|
177
|
+
|
|
178
|
+
const [writeN, writeErr] = file.Write(new Uint8Array([21, 22, 23]))
|
|
179
|
+
expect(writeN).toBe(3)
|
|
180
|
+
expect(writeErr).toBeNull()
|
|
181
|
+
expect(handle.writeSync).toHaveBeenCalledTimes(2)
|
|
182
|
+
expect(Array.from(handle.writeSync.mock.calls[1][0])).toEqual([22, 23])
|
|
183
|
+
})
|
|
131
184
|
})
|