goscript 0.2.4 → 0.2.5

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 (117) hide show
  1. package/README.md +8 -8
  2. package/cmd/go_js_wasm_exec/main.go +1 -1
  3. package/cmd/go_js_wasm_exec/main_test.go +1 -1
  4. package/cmd/goscript/cmd-compile.go +9 -1
  5. package/cmd/goscript/cmd-test.go +1 -1
  6. package/cmd/goscript/cmd_compile_test.go +44 -0
  7. package/cmd/goscript/deps.go +1 -1
  8. package/cmd/goscript-wasm/main.go +2 -2
  9. package/compiler/compile-request.go +19 -0
  10. package/compiler/compile_bench_test.go +121 -0
  11. package/compiler/compliance_test.go +17 -1
  12. package/compiler/config.go +2 -0
  13. package/compiler/gotest/result.go +1 -1
  14. package/compiler/gotest/runner.go +2 -2
  15. package/compiler/gotest/runner_test.go +4 -7
  16. package/compiler/index.test.ts +28 -0
  17. package/compiler/index.ts +32 -16
  18. package/compiler/lowering.go +1238 -194
  19. package/compiler/lowering_bench_test.go +4 -0
  20. package/compiler/override-facts.go +1 -1
  21. package/compiler/package-graph.go +92 -0
  22. package/compiler/package-graph_test.go +113 -0
  23. package/compiler/runtime-contract.go +1 -1
  24. package/compiler/semantic-model.go +32 -0
  25. package/compiler/skeleton_test.go +241 -15
  26. package/compiler/wasm/compile.go +1 -1
  27. package/compiler/wasm/compile_test.go +1 -1
  28. package/dist/compiler/index.d.ts +4 -0
  29. package/dist/compiler/index.js +26 -15
  30. package/dist/compiler/index.js.map +1 -1
  31. package/dist/gs/database/sql/driver/index.d.ts +165 -0
  32. package/dist/gs/database/sql/driver/index.js +432 -0
  33. package/dist/gs/database/sql/driver/index.js.map +1 -0
  34. package/dist/gs/encoding/binary/index.d.ts +71 -0
  35. package/dist/gs/encoding/binary/index.js +778 -0
  36. package/dist/gs/encoding/binary/index.js.map +1 -0
  37. package/dist/gs/fmt/fmt.js +156 -57
  38. package/dist/gs/fmt/fmt.js.map +1 -1
  39. package/dist/gs/github.com/klauspost/cpuid/v2/index.d.ts +11 -0
  40. package/dist/gs/github.com/klauspost/cpuid/v2/index.js +28 -0
  41. package/dist/gs/github.com/klauspost/cpuid/v2/index.js.map +1 -0
  42. package/dist/gs/github.com/pkg/errors/errors.d.ts +0 -2
  43. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  44. package/dist/gs/github.com/pkg/errors/index.d.ts +2 -1
  45. package/dist/gs/github.com/pkg/errors/index.js +1 -1
  46. package/dist/gs/github.com/pkg/errors/index.js.map +1 -1
  47. package/dist/gs/github.com/pkg/errors/stack.d.ts +8 -19
  48. package/dist/gs/github.com/pkg/errors/stack.js +26 -61
  49. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  50. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.d.ts +19 -0
  51. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js +25 -0
  52. package/dist/gs/golang.org/x/crypto/cryptobyte/asn1/index.js.map +1 -0
  53. package/dist/gs/golang.org/x/crypto/cryptobyte/index.d.ts +104 -0
  54. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1107 -0
  55. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -0
  56. package/dist/gs/golang.org/x/crypto/internal/alias/index.d.ts +3 -0
  57. package/dist/gs/golang.org/x/crypto/internal/alias/index.js +39 -0
  58. package/dist/gs/golang.org/x/crypto/internal/alias/index.js.map +1 -0
  59. package/dist/gs/runtime/runtime.d.ts +6 -1
  60. package/dist/gs/runtime/runtime.js +15 -8
  61. package/dist/gs/runtime/runtime.js.map +1 -1
  62. package/dist/gs/runtime/trace/index.d.ts +8 -5
  63. package/dist/gs/runtime/trace/index.js +324 -23
  64. package/dist/gs/runtime/trace/index.js.map +1 -1
  65. package/dist/gs/slices/slices.d.ts +2 -1
  66. package/dist/gs/slices/slices.js +9 -3
  67. package/dist/gs/slices/slices.js.map +1 -1
  68. package/dist/gs/sort/search.gs.d.ts +3 -1
  69. package/dist/gs/sort/search.gs.js +18 -53
  70. package/dist/gs/sort/search.gs.js.map +1 -1
  71. package/dist/gs/sync/sync.d.ts +1 -1
  72. package/dist/gs/sync/sync.js +3 -0
  73. package/dist/gs/sync/sync.js.map +1 -1
  74. package/dist/gs/time/time.d.ts +22 -29
  75. package/dist/gs/time/time.js +111 -32
  76. package/dist/gs/time/time.js.map +1 -1
  77. package/dist/gs/unsafe/unsafe.d.ts +3 -2
  78. package/dist/gs/unsafe/unsafe.js.map +1 -1
  79. package/go.mod +7 -5
  80. package/go.sum +12 -26
  81. package/gs/database/sql/driver/index.test.ts +88 -0
  82. package/gs/database/sql/driver/index.ts +675 -0
  83. package/gs/database/sql/driver/meta.json +3 -0
  84. package/gs/database/sql/driver/parity.json +144 -0
  85. package/gs/encoding/binary/index.test.ts +239 -0
  86. package/gs/encoding/binary/index.ts +999 -0
  87. package/gs/encoding/binary/meta.json +9 -0
  88. package/gs/encoding/binary/parity.json +72 -0
  89. package/gs/fmt/fmt.test.ts +28 -0
  90. package/gs/fmt/fmt.ts +198 -61
  91. package/gs/fmt/meta.json +2 -1
  92. package/gs/github.com/klauspost/cpuid/v2/index.ts +38 -0
  93. package/gs/github.com/klauspost/cpuid/v2/meta.json +3 -0
  94. package/gs/github.com/pkg/errors/errors.ts +1 -2
  95. package/gs/github.com/pkg/errors/index.ts +2 -1
  96. package/gs/github.com/pkg/errors/stack.ts +34 -62
  97. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.test.ts +19 -0
  98. package/gs/golang.org/x/crypto/cryptobyte/asn1/index.ts +29 -0
  99. package/gs/golang.org/x/crypto/cryptobyte/index.test.ts +255 -0
  100. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1441 -0
  101. package/gs/golang.org/x/crypto/cryptobyte/meta.json +3 -0
  102. package/gs/golang.org/x/crypto/internal/alias/index.test.ts +40 -0
  103. package/gs/golang.org/x/crypto/internal/alias/index.ts +40 -0
  104. package/gs/runtime/runtime.test.ts +16 -0
  105. package/gs/runtime/runtime.ts +17 -9
  106. package/gs/runtime/trace/index.test.ts +113 -14
  107. package/gs/runtime/trace/index.ts +384 -34
  108. package/gs/runtime/trace/meta.json +1 -0
  109. package/gs/slices/slices.test.ts +24 -1
  110. package/gs/slices/slices.ts +14 -4
  111. package/gs/sort/meta.json +1 -0
  112. package/gs/sort/search.gs.ts +20 -5
  113. package/gs/sync/sync.ts +4 -1
  114. package/gs/time/time.test.ts +79 -2
  115. package/gs/time/time.ts +133 -33
  116. package/gs/unsafe/unsafe.ts +4 -2
  117. package/package.json +2 -2
@@ -1,17 +1,132 @@
1
1
  import * as $ from '@goscript/builtin/index.js'
2
2
  import * as context from '@goscript/context/index.js'
3
3
  import * as errors from '@goscript/errors/index.js'
4
+ import * as fmt from '@goscript/fmt/index.js'
4
5
  import * as io from '@goscript/io/index.js'
5
6
 
7
+ // traceContextKey keys the active *Task stored in a context by NewTask.
6
8
  class traceContextKey {}
7
9
 
8
- const traceState = {
9
- nextTaskID: 0,
10
+ // Go execution trace v2 event type codes (iota order from the runtime spec).
11
+ // GoScript emits only the user-task subset plus the minimal proc/goroutine
12
+ // status needed for the upstream trace reader to bind a running context.
13
+ const evEventBatch = 1
14
+ const evStrings = 4
15
+ const evString = 5
16
+ const evFrequency = 8
17
+ const evProcStatus = 13
18
+ const evGoStatus = 25
19
+ const evUserTaskBegin = 40
20
+ const evUserTaskEnd = 41
21
+ const evUserRegionBegin = 42
22
+ const evUserRegionEnd = 43
23
+ const evUserLog = 44
24
+
25
+ // procRunning and goRunning are the running status codes for the synthetic P
26
+ // and G that own every GoScript user event (GoScript has no real scheduler).
27
+ const procRunning = 1
28
+ const goRunning = 2
29
+
30
+ // traceGen, traceProc, and traceGoroutine are the fixed generation, P, and G
31
+ // identities for the single synthetic GoScript execution context.
32
+ const traceGen = 1
33
+ const traceMID = 0
34
+ const traceProc = 0
35
+ const traceGoroutine = 1
36
+
37
+ // traceFreqHz is the timestamp frequency reported to the reader. GoScript
38
+ // records nanosecond timestamps, so the frequency is one billion ticks/sec
39
+ // and the reader's tick->nanosecond conversion is the identity.
40
+ const traceFreqHz = 1_000_000_000
41
+
42
+ // maxBatchData bounds the post-header data block of every per-M batch. The
43
+ // trace v2 reader rejects any batch whose declared size exceeds 64 KiB
44
+ // (tracev2.MaxBatchSize = 64<<10), so a realistic capture must split its string
45
+ // and event records across multiple batches. The cap sits below the hard limit
46
+ // to leave margin for the next record, which is written in full before the size
47
+ // is rechecked.
48
+ const maxBatchData = 60 * 1024
49
+
50
+ // maxEventBytes is a safe upper bound on the encoded size of one user event
51
+ // (kind byte plus a handful of base-128 varints; strings are interned, so no
52
+ // inline payload). The event loop flushes the current batch before its data
53
+ // block can grow within this margin of maxBatchData.
54
+ const maxEventBytes = 128
55
+
56
+ // recEvent kinds.
57
+ const kindTaskBegin = 0
58
+ const kindTaskEnd = 1
59
+ const kindRegionBegin = 2
60
+ const kindRegionEnd = 3
61
+ const kindLog = 4
62
+
63
+ // recEvent is one buffered user trace event with its monotonic timestamp.
64
+ interface recEvent {
65
+ kind: number
66
+ ts: number
67
+ task: number
68
+ parent: number
69
+ s0: string
70
+ s1: string
71
+ }
72
+
73
+ // recorder buffers user trace events between Start and Stop. GoScript has no
74
+ // streaming runtime, so events accumulate in memory and the full trace v2 byte
75
+ // stream is written to the output writer on Stop.
76
+ interface recorder {
77
+ enabled: boolean
78
+ writer: io.Writer | null
79
+ startTs: number
80
+ events: recEvent[]
10
81
  }
11
82
 
12
- const unsupportedTraceError = errors.New(
13
- 'runtime/trace: execution tracing is unsupported in GoScript',
14
- )
83
+ function newRecorder(): recorder {
84
+ return { enabled: false, writer: null, startTs: 0, events: [] }
85
+ }
86
+
87
+ // rec is the active capture. nextTaskID is process-global and monotonic like
88
+ // the Go runtime task counter, so it persists across Start/Stop cycles.
89
+ let rec = newRecorder()
90
+ let nextTaskID = 0
91
+
92
+ // nowNs reads the high-resolution monotonic clock in nanoseconds, the same
93
+ // source that backs time.Time monotonic readings.
94
+ function nowNs(): number {
95
+ if (typeof performance !== 'undefined' && performance.now) {
96
+ return performance.now() * 1_000_000
97
+ }
98
+ return globalThis.Date.now() * 1_000_000
99
+ }
100
+
101
+ function contextKey(): any {
102
+ return $.interfaceValue(
103
+ $.markAsStructValue(new traceContextKey()),
104
+ 'trace.traceContextKey',
105
+ )
106
+ }
107
+
108
+ // taskIDFromContext returns the active task ID carried by ctx, or 0 when ctx
109
+ // carries no task (the background task).
110
+ function taskIDFromContext(ctx: context.Context | null): number {
111
+ if (ctx == null) {
112
+ return 0
113
+ }
114
+ const value = ctx.Value(contextKey())
115
+ if (value instanceof Task) {
116
+ return value.id
117
+ }
118
+ return 0
119
+ }
120
+
121
+ function record(
122
+ kind: number,
123
+ task: number,
124
+ parent: number,
125
+ s0: string,
126
+ s1: string,
127
+ ): void {
128
+ rec.events.push({ kind, ts: nowNs(), task, parent, s0, s1 })
129
+ }
15
130
 
16
131
  export class Task {
17
132
  public id: number
@@ -20,62 +135,93 @@ export class Task {
20
135
  this.id = id
21
136
  }
22
137
 
23
- public End(): void {}
138
+ public End(): void {
139
+ if (rec.enabled) {
140
+ record(kindTaskEnd, this.id, 0, '', '')
141
+ }
142
+ }
24
143
  }
25
144
 
26
145
  export class Region {
27
- public End(): void {}
146
+ private task: number
147
+ private name: string
148
+
149
+ constructor(task = 0, name = '') {
150
+ this.task = task
151
+ this.name = name
152
+ }
153
+
154
+ public End(): void {
155
+ if (rec.enabled) {
156
+ record(kindRegionEnd, this.task, 0, this.name, '')
157
+ }
158
+ }
28
159
  }
29
160
 
30
161
  export function NewTask(
31
162
  pctx: context.Context | null,
32
- _taskType: string,
163
+ taskType: string,
33
164
  ): [context.Context | null, Task] {
34
165
  const parent = pctx ?? context.Background()
35
- const task = new Task(++traceState.nextTaskID)
36
- return [
37
- context.WithValue(
38
- parent,
39
- $.interfaceValue(new traceContextKey(), 'trace.traceContextKey'),
40
- $.interfaceValue(task, '*trace.Task'),
41
- ),
42
- task,
43
- ]
166
+ const task = new Task(++nextTaskID)
167
+ if (rec.enabled) {
168
+ record(kindTaskBegin, task.id, taskIDFromContext(pctx), taskType, '')
169
+ }
170
+ return [context.WithValue(parent, contextKey(), task), task]
44
171
  }
45
172
 
46
173
  export function Log(
47
- _ctx: context.Context | null,
174
+ ctx: context.Context | null,
48
175
  category: string,
49
176
  message: string,
50
177
  ): void {
51
- void category
52
- void message
178
+ if (rec.enabled) {
179
+ record(kindLog, taskIDFromContext(ctx), 0, category, message)
180
+ }
53
181
  }
54
182
 
55
183
  export function Logf(
56
- _ctx: context.Context | null,
57
- _category: string,
58
- _format: string,
59
- ..._args: unknown[]
60
- ): void {}
184
+ ctx: context.Context | null,
185
+ category: string,
186
+ format: string,
187
+ ...args: unknown[]
188
+ ): void {
189
+ if (rec.enabled) {
190
+ Log(ctx, category, fmt.Sprintf(format, ...(args as unknown[])))
191
+ }
192
+ }
61
193
 
62
194
  export function WithRegion(
63
- _ctx: context.Context | null,
64
- _regionType: string,
195
+ ctx: context.Context | null,
196
+ regionType: string,
65
197
  fn: (() => void) | null,
66
198
  ): void {
67
- fn?.()
199
+ const task = taskIDFromContext(ctx)
200
+ if (rec.enabled) {
201
+ record(kindRegionBegin, task, 0, regionType, '')
202
+ }
203
+ try {
204
+ fn?.()
205
+ } finally {
206
+ if (rec.enabled) {
207
+ record(kindRegionEnd, task, 0, regionType, '')
208
+ }
209
+ }
68
210
  }
69
211
 
70
212
  export function StartRegion(
71
- _ctx: context.Context | null,
72
- _regionType: string,
213
+ ctx: context.Context | null,
214
+ regionType: string,
73
215
  ): Region {
74
- return new Region()
216
+ const task = taskIDFromContext(ctx)
217
+ if (rec.enabled) {
218
+ record(kindRegionBegin, task, 0, regionType, '')
219
+ }
220
+ return new Region(task, regionType)
75
221
  }
76
222
 
77
223
  export function IsEnabled(): boolean {
78
- return false
224
+ return rec.enabled
79
225
  }
80
226
 
81
227
  export function Start(w: io.Writer | $.VarRef<io.Writer> | null): $.GoError {
@@ -83,7 +229,211 @@ export function Start(w: io.Writer | $.VarRef<io.Writer> | null): $.GoError {
83
229
  if (writer == null) {
84
230
  return errors.New('runtime/trace: nil trace writer')
85
231
  }
86
- return unsupportedTraceError
232
+ if (rec.enabled) {
233
+ return errors.New('runtime/trace: tracing already enabled')
234
+ }
235
+ rec = newRecorder()
236
+ rec.enabled = true
237
+ rec.writer = writer
238
+ rec.startTs = nowNs()
239
+ return null
240
+ }
241
+
242
+ export function Stop(): void {
243
+ if (!rec.enabled) {
244
+ return
245
+ }
246
+ const writer = rec.writer
247
+ const buffered = rec
248
+ rec = newRecorder()
249
+ const payload = encodeTrace(buffered)
250
+ if (writer != null) {
251
+ writer.Write(payload)
252
+ }
253
+ }
254
+
255
+ // putUvarint appends value to out as a base-128 varint. It uses arithmetic
256
+ // rather than bit operations so values above 2^32 (nanosecond timestamps)
257
+ // encode correctly.
258
+ function putUvarint(out: number[], value: number): void {
259
+ let v = Math.floor(value)
260
+ if (v < 0) {
261
+ v = 0
262
+ }
263
+ while (v >= 0x80) {
264
+ out.push((v % 0x80) + 0x80)
265
+ v = Math.floor(v / 0x80)
266
+ }
267
+ out.push(v)
268
+ }
269
+
270
+ function utf8Bytes(s: string): Uint8Array {
271
+ if (typeof TextEncoder !== 'undefined') {
272
+ return new TextEncoder().encode(s)
273
+ }
274
+ const out = new Uint8Array(s.length)
275
+ for (let i = 0; i < s.length; i++) {
276
+ out[i] = s.charCodeAt(i) & 0xff
277
+ }
278
+ return out
279
+ }
280
+
281
+ // appendBatch frames data as a trace v2 per-M batch (EvEventBatch header plus
282
+ // the generation, M, base timestamp, and length prefix).
283
+ function appendBatch(out: number[], data: number[]): void {
284
+ out.push(evEventBatch)
285
+ putUvarint(out, traceGen)
286
+ putUvarint(out, traceMID)
287
+ putUvarint(out, 0)
288
+ putUvarint(out, data.length)
289
+ for (const b of data) {
290
+ out.push(b)
291
+ }
87
292
  }
88
293
 
89
- export function Stop(): void {}
294
+ // encodeTrace serializes the buffered user events as a single-generation Go
295
+ // trace v2 byte stream containing only the user-task subset.
296
+ function encodeTrace(r: recorder): Uint8Array {
297
+ const out: number[] = []
298
+
299
+ // Header: "go 1.22 trace\x00\x00\x00". The reader selects the Go 1.22 event
300
+ // table, whose final event is EvUserLog, covering every event GoScript emits.
301
+ const header = 'go 1.22 trace\x00\x00\x00'
302
+ for (let i = 0; i < header.length; i++) {
303
+ out.push(header.charCodeAt(i) & 0xff)
304
+ }
305
+
306
+ // Frequency batch (required): ticks per second.
307
+ const freq: number[] = [evFrequency]
308
+ putUvarint(freq, traceFreqHz)
309
+ appendBatch(out, freq)
310
+
311
+ // Intern the strings referenced by user events.
312
+ const stringIDs = new Map<string, number>()
313
+ const internedStrings: string[] = []
314
+ const intern = (s: string): number => {
315
+ let id = stringIDs.get(s)
316
+ if (id === undefined) {
317
+ id = internedStrings.length + 1
318
+ stringIDs.set(s, id)
319
+ internedStrings.push(s)
320
+ }
321
+ return id
322
+ }
323
+ for (const ev of r.events) {
324
+ if (
325
+ ev.kind === kindTaskBegin ||
326
+ ev.kind === kindRegionBegin ||
327
+ ev.kind === kindRegionEnd
328
+ ) {
329
+ intern(ev.s0)
330
+ } else if (ev.kind === kindLog) {
331
+ intern(ev.s0)
332
+ intern(ev.s1)
333
+ }
334
+ }
335
+
336
+ // Strings batches. The reader concatenates the string dictionary across every
337
+ // EvStrings batch in the generation, so a large table splits into multiple
338
+ // batches whose data blocks each stay under maxBatchData. Each EvString record
339
+ // (tag, id, length, bytes) stays whole within one batch.
340
+ if (internedStrings.length > 0) {
341
+ let strings: number[] = [evStrings]
342
+ for (let i = 0; i < internedStrings.length; i++) {
343
+ const bytes = utf8Bytes(internedStrings[i])
344
+ const recordSize = 1 + 10 + 10 + bytes.length
345
+ if (strings.length > 1 && strings.length + recordSize > maxBatchData) {
346
+ appendBatch(out, strings)
347
+ strings = [evStrings]
348
+ }
349
+ strings.push(evString)
350
+ putUvarint(strings, i + 1)
351
+ putUvarint(strings, bytes.length)
352
+ for (const b of bytes) {
353
+ strings.push(b)
354
+ }
355
+ }
356
+ appendBatch(out, strings)
357
+ }
358
+
359
+ // Event batches: synthetic running P and G, then any user events in order. The
360
+ // running context is emitted even with no user events, so every capture is a
361
+ // complete single-generation trace the reader accepts rather than a bare
362
+ // header plus frequency batch. User events split across multiple per-M batches
363
+ // so no data block exceeds maxBatchData. The reader resets its delta clock to
364
+ // each batch's base timestamp (0 here), so flushing resets lastTs to 0 and the
365
+ // first event of every batch re-emits its absolute offset as the delta.
366
+ let data: number[] = []
367
+ let lastTs = 0
368
+ const emitDelta = (ts: number): void => {
369
+ const offset = Math.max(0, Math.floor(ts - r.startTs))
370
+ const dt = Math.max(0, offset - lastTs)
371
+ lastTs = offset
372
+ putUvarint(data, dt)
373
+ }
374
+ const flushEventBatch = (): void => {
375
+ appendBatch(out, data)
376
+ data = []
377
+ lastTs = 0
378
+ }
379
+
380
+ // EvProcStatus(dt, p, status): bind the running P to the context.
381
+ data.push(evProcStatus)
382
+ putUvarint(data, 0)
383
+ putUvarint(data, traceProc)
384
+ putUvarint(data, procRunning)
385
+
386
+ // EvGoStatus(dt, g, m, status): bind the running G to the context.
387
+ data.push(evGoStatus)
388
+ putUvarint(data, 0)
389
+ putUvarint(data, traceGoroutine)
390
+ putUvarint(data, traceMID)
391
+ putUvarint(data, goRunning)
392
+
393
+ for (const ev of r.events) {
394
+ if (data.length + maxEventBytes > maxBatchData) {
395
+ flushEventBatch()
396
+ }
397
+ switch (ev.kind) {
398
+ case kindTaskBegin:
399
+ data.push(evUserTaskBegin)
400
+ emitDelta(ev.ts)
401
+ putUvarint(data, ev.task)
402
+ putUvarint(data, ev.parent)
403
+ putUvarint(data, intern(ev.s0))
404
+ putUvarint(data, 0)
405
+ break
406
+ case kindTaskEnd:
407
+ data.push(evUserTaskEnd)
408
+ emitDelta(ev.ts)
409
+ putUvarint(data, ev.task)
410
+ putUvarint(data, 0)
411
+ break
412
+ case kindRegionBegin:
413
+ data.push(evUserRegionBegin)
414
+ emitDelta(ev.ts)
415
+ putUvarint(data, ev.task)
416
+ putUvarint(data, intern(ev.s0))
417
+ putUvarint(data, 0)
418
+ break
419
+ case kindRegionEnd:
420
+ data.push(evUserRegionEnd)
421
+ emitDelta(ev.ts)
422
+ putUvarint(data, ev.task)
423
+ putUvarint(data, intern(ev.s0))
424
+ putUvarint(data, 0)
425
+ break
426
+ case kindLog:
427
+ data.push(evUserLog)
428
+ emitDelta(ev.ts)
429
+ putUvarint(data, ev.task)
430
+ putUvarint(data, intern(ev.s0))
431
+ putUvarint(data, intern(ev.s1))
432
+ putUvarint(data, 0)
433
+ break
434
+ }
435
+ }
436
+ flushEventBatch()
437
+
438
+ return new Uint8Array(out)
439
+ }
@@ -2,6 +2,7 @@
2
2
  "dependencies": [
3
3
  "context",
4
4
  "errors",
5
+ "fmt",
5
6
  "io"
6
7
  ]
7
8
  }
@@ -4,6 +4,7 @@ import * as $ from '@goscript/builtin/index.js'
4
4
 
5
5
  import {
6
6
  All,
7
+ AppendSeq,
7
8
  Backward,
8
9
  BinarySearch,
9
10
  Clip,
@@ -143,7 +144,29 @@ describe('slices.Sorted', () => {
143
144
  yieldValue('b')
144
145
  })
145
146
 
146
- expect(values).toEqual(['a', 'b', 'c'])
147
+ expect(Array.from(values ?? [])).toEqual(['a', 'b', 'c'])
148
+ })
149
+ })
150
+
151
+ describe('slices.AppendSeq', () => {
152
+ it('appends iterator values to an existing slice', () => {
153
+ const values = AppendSeq($.arrayToSlice([1]), (yieldValue) => {
154
+ yieldValue(2)
155
+ yieldValue(3)
156
+ })
157
+
158
+ expect(Array.from(values ?? [])).toEqual([1, 2, 3])
159
+ })
160
+
161
+ it('collects into a nil slice and preserves nilness for empty sequences', () => {
162
+ const values = AppendSeq<number>(null, (yieldValue) => {
163
+ yieldValue(4)
164
+ yieldValue(5)
165
+ })
166
+ const empty = AppendSeq<number>(null, () => {})
167
+
168
+ expect(Array.from(values ?? [])).toEqual([4, 5])
169
+ expect(empty).toBeNull()
147
170
  })
148
171
  })
149
172
 
@@ -266,13 +266,23 @@ export function MinFunc<T>(x: $.Slice<T>, compare: CompareCallback<T>): T {
266
266
  return min
267
267
  }
268
268
 
269
- export function Collect<T>(seq: iter.Seq<T>): $.Slice<T> {
270
- const out: T[] = []
269
+ export function Collect<T>(seq: iter.Seq<T> | null): $.Slice<T> {
270
+ return AppendSeq(null, seq)
271
+ }
272
+
273
+ export function AppendSeq<T>(
274
+ s: $.Slice<T>,
275
+ seq: iter.Seq<T> | null,
276
+ ): $.Slice<T> {
277
+ if (seq == null) {
278
+ throw new Error('slices: nil iterator')
279
+ }
280
+ let out = s
271
281
  seq((value: T) => {
272
- out.push(value)
282
+ out = $.append(out, value)
273
283
  return true
274
284
  })
275
- return out.length === 0 ? null : out
285
+ return out
276
286
  }
277
287
 
278
288
  export function Sorted<T extends string | number>(
package/gs/sort/meta.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "asyncFunctions": {
3
+ "Search": true,
3
4
  "Slice": true,
4
5
  "SliceIsSorted": true,
5
6
  "SliceStable": true
@@ -49,7 +49,23 @@ import * as $ from "@goscript/builtin/index.js";
49
49
  // })
50
50
  // fmt.Printf("Your number is %d.\n", answer)
51
51
  // }
52
- export function Search(n: number, f: (i: number) => boolean): number {
52
+ type SearchPredicate = (i: number) => boolean | Promise<boolean>
53
+
54
+ export async function Search(n: number, f: SearchPredicate): Promise<number> {
55
+ let left = 0
56
+ let right = n
57
+ while (left < right) {
58
+ const mid = Math.floor((left + right) / 2)
59
+ if (await f(mid)) {
60
+ right = mid
61
+ } else {
62
+ left = mid + 1
63
+ }
64
+ }
65
+ return left
66
+ }
67
+
68
+ function searchSync(n: number, f: (i: number) => boolean): number {
53
69
  let left = 0
54
70
  let right = n
55
71
  while (left < right) {
@@ -107,7 +123,7 @@ export function Find(n: number, cmp: (i: number) => number): [number, boolean] {
107
123
  // not present (it could be len(a)).
108
124
  // The slice must be sorted in ascending order.
109
125
  export function SearchInts(a: $.Slice<number>, x: number): number {
110
- return Search($.len(a), (i: number) => ($.index(a, i) as number) >= x)
126
+ return searchSync($.len(a), (i: number) => ($.index(a, i) as number) >= x)
111
127
  }
112
128
 
113
129
  // SearchFloat64s searches for x in a sorted slice of float64s and returns the index
@@ -115,7 +131,7 @@ export function SearchInts(a: $.Slice<number>, x: number): number {
115
131
  // present (it could be len(a)).
116
132
  // The slice must be sorted in ascending order.
117
133
  export function SearchFloat64s(a: $.Slice<number>, x: number): number {
118
- return Search($.len(a), (i: number) => ($.index(a, i) as number) >= x)
134
+ return searchSync($.len(a), (i: number) => ($.index(a, i) as number) >= x)
119
135
  }
120
136
 
121
137
  // SearchStrings searches for x in a sorted slice of strings and returns the index
@@ -123,6 +139,5 @@ export function SearchFloat64s(a: $.Slice<number>, x: number): number {
123
139
  // present (it could be len(a)).
124
140
  // The slice must be sorted in ascending order.
125
141
  export function SearchStrings(a: $.Slice<string>, x: string): number {
126
- return Search($.len(a), (i: number) => ($.index(a, i) as string) >= x)
142
+ return searchSync($.len(a), (i: number) => ($.index(a, i) as string) >= x)
127
143
  }
128
-
package/gs/sync/sync.ts CHANGED
@@ -243,7 +243,10 @@ export class Once {
243
243
  }
244
244
 
245
245
  // Do calls the function f if and only if Do is being called for the first time for this instance of Once
246
- public async Do(f: () => void | Promise<void>): Promise<void> {
246
+ public async Do(f: (() => void | Promise<void>) | null): Promise<void> {
247
+ if (f == null) {
248
+ throw new Error('sync: nil function in Once.Do')
249
+ }
247
250
  if (this._done) {
248
251
  return
249
252
  }