goscript 0.2.5 → 0.2.7

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 (264) hide show
  1. package/cmd/goscript/cmd-compile.go +7 -0
  2. package/cmd/goscript/cmd_compile_test.go +83 -0
  3. package/compiler/compile-request.go +3 -0
  4. package/compiler/compiler-cache.go +828 -0
  5. package/compiler/compiler-cache_test.go +705 -0
  6. package/compiler/config.go +2 -0
  7. package/compiler/index.test.ts +26 -1
  8. package/compiler/index.ts +5 -0
  9. package/compiler/lowered-program.go +31 -20
  10. package/compiler/lowering.go +349 -93
  11. package/compiler/lowering_bench_test.go +1 -0
  12. package/compiler/override-facts.go +309 -8
  13. package/compiler/override-parity-verifier.go +45 -1
  14. package/compiler/override-parity-verifier_test.go +100 -0
  15. package/compiler/override-registry_test.go +1 -0
  16. package/compiler/package-graph.go +40 -12
  17. package/compiler/package-graph_test.go +29 -0
  18. package/compiler/runtime-contract.go +8 -0
  19. package/compiler/service.go +98 -11
  20. package/compiler/skeleton_test.go +110 -14
  21. package/compiler/typescript-emitter.go +120 -23
  22. package/dist/compiler/index.d.ts +2 -0
  23. package/dist/compiler/index.js +3 -0
  24. package/dist/compiler/index.js.map +1 -1
  25. package/dist/gs/builtin/builtin.d.ts +24 -33
  26. package/dist/gs/builtin/builtin.js +54 -61
  27. package/dist/gs/builtin/builtin.js.map +1 -1
  28. package/dist/gs/builtin/hostio.d.ts +1 -0
  29. package/dist/gs/builtin/hostio.js +1 -1
  30. package/dist/gs/builtin/hostio.js.map +1 -1
  31. package/dist/gs/builtin/index.d.ts +1 -0
  32. package/dist/gs/builtin/index.js +1 -0
  33. package/dist/gs/builtin/index.js.map +1 -1
  34. package/dist/gs/builtin/panic.d.ts +18 -0
  35. package/dist/gs/builtin/panic.js +98 -0
  36. package/dist/gs/builtin/panic.js.map +1 -0
  37. package/dist/gs/builtin/slice.d.ts +10 -0
  38. package/dist/gs/builtin/slice.js +110 -53
  39. package/dist/gs/builtin/slice.js.map +1 -1
  40. package/dist/gs/builtin/type.js +15 -3
  41. package/dist/gs/builtin/type.js.map +1 -1
  42. package/dist/gs/builtin/varRef.d.ts +1 -1
  43. package/dist/gs/builtin/varRef.js +3 -2
  44. package/dist/gs/builtin/varRef.js.map +1 -1
  45. package/dist/gs/bytes/bytes.gs.js +51 -38
  46. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  47. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  48. package/dist/gs/bytes/reader.gs.js +6 -7
  49. package/dist/gs/bytes/reader.gs.js.map +1 -1
  50. package/dist/gs/cmp/index.d.ts +1 -1
  51. package/dist/gs/cmp/index.js +43 -10
  52. package/dist/gs/cmp/index.js.map +1 -1
  53. package/dist/gs/context/context.d.ts +2 -2
  54. package/dist/gs/context/context.js +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/embed/index.js +1 -1
  57. package/dist/gs/embed/index.js.map +1 -1
  58. package/dist/gs/encoding/binary/index.js +201 -8
  59. package/dist/gs/encoding/binary/index.js.map +1 -1
  60. package/dist/gs/encoding/json/index.d.ts +5 -0
  61. package/dist/gs/encoding/json/index.js +388 -25
  62. package/dist/gs/encoding/json/index.js.map +1 -1
  63. package/dist/gs/errors/errors.js +17 -24
  64. package/dist/gs/errors/errors.js.map +1 -1
  65. package/dist/gs/fmt/fmt.js +129 -35
  66. package/dist/gs/fmt/fmt.js.map +1 -1
  67. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1 -1
  68. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -1
  69. package/dist/gs/internal/bytealg/index.js +43 -8
  70. package/dist/gs/internal/bytealg/index.js.map +1 -1
  71. package/dist/gs/internal/byteorder/index.d.ts +2 -2
  72. package/dist/gs/internal/byteorder/index.js +2 -2
  73. package/dist/gs/internal/byteorder/index.js.map +1 -1
  74. package/dist/gs/io/fs/format.js +2 -2
  75. package/dist/gs/io/fs/format.js.map +1 -1
  76. package/dist/gs/io/fs/fs.d.ts +1 -1
  77. package/dist/gs/io/fs/fs.js +1 -1
  78. package/dist/gs/io/fs/fs.js.map +1 -1
  79. package/dist/gs/io/io.d.ts +21 -21
  80. package/dist/gs/io/io.js +49 -50
  81. package/dist/gs/io/io.js.map +1 -1
  82. package/dist/gs/math/bits/index.js +26 -8
  83. package/dist/gs/math/bits/index.js.map +1 -1
  84. package/dist/gs/math/copysign.gs.js +10 -17
  85. package/dist/gs/math/copysign.gs.js.map +1 -1
  86. package/dist/gs/math/pow.gs.js +5 -0
  87. package/dist/gs/math/pow.gs.js.map +1 -1
  88. package/dist/gs/math/signbit.gs.js +6 -2
  89. package/dist/gs/math/signbit.gs.js.map +1 -1
  90. package/dist/gs/mime/index.js +1 -0
  91. package/dist/gs/mime/index.js.map +1 -1
  92. package/dist/gs/net/http/index.d.ts +6 -6
  93. package/dist/gs/net/http/index.js +507 -43
  94. package/dist/gs/net/http/index.js.map +1 -1
  95. package/dist/gs/os/stat.gs.d.ts +2 -2
  96. package/dist/gs/os/types.gs.d.ts +1 -1
  97. package/dist/gs/os/types.gs.js +1 -1
  98. package/dist/gs/os/types.gs.js.map +1 -1
  99. package/dist/gs/os/types_js.gs.d.ts +1 -1
  100. package/dist/gs/os/types_js.gs.js +7 -7
  101. package/dist/gs/os/types_js.gs.js.map +1 -1
  102. package/dist/gs/os/types_unix.gs.d.ts +1 -1
  103. package/dist/gs/os/types_unix.gs.js +1 -1
  104. package/dist/gs/os/types_unix.gs.js.map +1 -1
  105. package/dist/gs/os/zero_copy_posix.gs.d.ts +1 -1
  106. package/dist/gs/os/zero_copy_posix.gs.js +1 -1
  107. package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
  108. package/dist/gs/path/filepath/match.js +8 -4
  109. package/dist/gs/path/filepath/match.js.map +1 -1
  110. package/dist/gs/path/filepath/path.js +216 -42
  111. package/dist/gs/path/filepath/path.js.map +1 -1
  112. package/dist/gs/path/match.js +6 -3
  113. package/dist/gs/path/match.js.map +1 -1
  114. package/dist/gs/reflect/type.d.ts +5 -4
  115. package/dist/gs/reflect/type.js +29 -11
  116. package/dist/gs/reflect/type.js.map +1 -1
  117. package/dist/gs/slices/slices.js +11 -11
  118. package/dist/gs/slices/slices.js.map +1 -1
  119. package/dist/gs/strconv/atof.gs.js +156 -43
  120. package/dist/gs/strconv/atof.gs.js.map +1 -1
  121. package/dist/gs/strconv/atoi.gs.d.ts +3 -2
  122. package/dist/gs/strconv/atoi.gs.js +86 -67
  123. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  124. package/dist/gs/strconv/ftoa.gs.js +73 -3
  125. package/dist/gs/strconv/ftoa.gs.js.map +1 -1
  126. package/dist/gs/strconv/itoa.gs.d.ts +4 -4
  127. package/dist/gs/strconv/itoa.gs.js +5 -4
  128. package/dist/gs/strconv/itoa.gs.js.map +1 -1
  129. package/dist/gs/strconv/quote.gs.d.ts +1 -1
  130. package/dist/gs/strconv/quote.gs.js +311 -103
  131. package/dist/gs/strconv/quote.gs.js.map +1 -1
  132. package/dist/gs/strings/reader.d.ts +1 -1
  133. package/dist/gs/strings/reader.js +8 -8
  134. package/dist/gs/strings/reader.js.map +1 -1
  135. package/dist/gs/strings/strings.js +87 -61
  136. package/dist/gs/strings/strings.js.map +1 -1
  137. package/dist/gs/sync/atomic/doc_64.gs.d.ts +14 -14
  138. package/dist/gs/sync/atomic/doc_64.gs.js +10 -10
  139. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  140. package/dist/gs/sync/atomic/type.gs.d.ts +22 -22
  141. package/dist/gs/sync/atomic/type.gs.js +4 -4
  142. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  143. package/dist/gs/sync/sync.js +50 -12
  144. package/dist/gs/sync/sync.js.map +1 -1
  145. package/dist/gs/syscall/fs.d.ts +6 -6
  146. package/dist/gs/syscall/fs.js +1 -1
  147. package/dist/gs/syscall/fs.js.map +1 -1
  148. package/dist/gs/time/time.d.ts +18 -18
  149. package/dist/gs/time/time.js +58 -55
  150. package/dist/gs/time/time.js.map +1 -1
  151. package/dist/gs/unicode/tables.d.ts +11 -0
  152. package/dist/gs/unicode/tables.js +635 -0
  153. package/dist/gs/unicode/tables.js.map +1 -0
  154. package/dist/gs/unicode/unicode.d.ts +58 -38
  155. package/dist/gs/unicode/unicode.js +362 -278
  156. package/dist/gs/unicode/unicode.js.map +1 -1
  157. package/go.sum +13 -0
  158. package/gs/builtin/builtin.ts +83 -93
  159. package/gs/builtin/hostio.ts +1 -1
  160. package/gs/builtin/index.ts +1 -0
  161. package/gs/builtin/panic.test.ts +189 -0
  162. package/gs/builtin/panic.ts +107 -0
  163. package/gs/builtin/runtime-contract.test.ts +5 -5
  164. package/gs/builtin/slice.test.ts +23 -0
  165. package/gs/builtin/slice.ts +133 -95
  166. package/gs/builtin/type.ts +16 -3
  167. package/gs/builtin/varRef.ts +4 -2
  168. package/gs/builtin/wide-int.test.ts +41 -0
  169. package/gs/bytes/bytes.gs.ts +54 -41
  170. package/gs/bytes/bytes.test.ts +18 -1
  171. package/gs/bytes/reader.gs.ts +7 -8
  172. package/gs/cmp/index.test.ts +55 -0
  173. package/gs/cmp/index.ts +45 -9
  174. package/gs/context/context.ts +3 -3
  175. package/gs/embed/index.ts +2 -2
  176. package/gs/encoding/binary/index.test.ts +104 -0
  177. package/gs/encoding/binary/index.ts +259 -11
  178. package/gs/encoding/json/index.test.ts +107 -0
  179. package/gs/encoding/json/index.ts +400 -29
  180. package/gs/errors/errors.test.ts +44 -1
  181. package/gs/errors/errors.ts +15 -31
  182. package/gs/fmt/fmt.test.ts +70 -2
  183. package/gs/fmt/fmt.ts +128 -34
  184. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1 -1
  185. package/gs/internal/bytealg/index.test.ts +26 -1
  186. package/gs/internal/bytealg/index.ts +44 -8
  187. package/gs/internal/byteorder/index.ts +6 -4
  188. package/gs/io/fs/format.ts +2 -2
  189. package/gs/io/fs/fs.ts +2 -2
  190. package/gs/io/fs/stat.test.ts +2 -2
  191. package/gs/io/fs/sub.test.ts +2 -2
  192. package/gs/io/fs/walk.test.ts +2 -2
  193. package/gs/io/io.test.ts +47 -5
  194. package/gs/io/io.ts +73 -73
  195. package/gs/io/limit.test.ts +103 -0
  196. package/gs/math/bits/index.test.ts +128 -0
  197. package/gs/math/bits/index.ts +26 -8
  198. package/gs/math/copysign.gs.test.ts +3 -1
  199. package/gs/math/copysign.gs.ts +10 -22
  200. package/gs/math/pow.gs.test.ts +4 -5
  201. package/gs/math/pow.gs.ts +5 -0
  202. package/gs/math/signbit.gs.test.ts +2 -1
  203. package/gs/math/signbit.gs.ts +6 -3
  204. package/gs/mime/index.ts +1 -0
  205. package/gs/net/http/index.test.ts +683 -2
  206. package/gs/net/http/index.ts +598 -57
  207. package/gs/net/http/meta.json +3 -0
  208. package/gs/os/stat.gs.ts +2 -2
  209. package/gs/os/types.gs.ts +2 -2
  210. package/gs/os/types_js.gs.ts +9 -9
  211. package/gs/os/types_unix.gs.ts +2 -2
  212. package/gs/os/zero_copy_posix.gs.ts +2 -2
  213. package/gs/path/filepath/match.test.ts +16 -0
  214. package/gs/path/filepath/match.ts +8 -4
  215. package/gs/path/filepath/path.test.ts +91 -9
  216. package/gs/path/filepath/path.ts +223 -49
  217. package/gs/path/match.test.ts +32 -0
  218. package/gs/path/match.ts +6 -3
  219. package/gs/reflect/deepequal.test.ts +1 -1
  220. package/gs/reflect/field.test.ts +1 -1
  221. package/gs/reflect/function-types.test.ts +6 -6
  222. package/gs/reflect/sliceat.test.ts +13 -13
  223. package/gs/reflect/structof.test.ts +4 -4
  224. package/gs/reflect/type.ts +34 -14
  225. package/gs/reflect/typefor.test.ts +5 -5
  226. package/gs/runtime/pprof/index.test.ts +20 -0
  227. package/gs/runtime/trace/index.test.ts +3 -0
  228. package/gs/slices/slices.test.ts +31 -0
  229. package/gs/slices/slices.ts +11 -11
  230. package/gs/strconv/append.test.ts +99 -0
  231. package/gs/strconv/atof.gs.ts +156 -42
  232. package/gs/strconv/atof.test.ts +45 -0
  233. package/gs/strconv/atoi.gs.ts +87 -69
  234. package/gs/strconv/atoi.test.ts +49 -0
  235. package/gs/strconv/ftoa.gs.ts +85 -10
  236. package/gs/strconv/ftoa.test.ts +43 -0
  237. package/gs/strconv/itoa.gs.ts +10 -9
  238. package/gs/strconv/quote.gs.ts +335 -108
  239. package/gs/strconv/quote.test.ts +111 -0
  240. package/gs/strings/reader.test.ts +10 -10
  241. package/gs/strings/reader.ts +9 -9
  242. package/gs/strings/strings.test.ts +18 -5
  243. package/gs/strings/strings.ts +81 -68
  244. package/gs/sync/atomic/doc_64.gs.ts +24 -24
  245. package/gs/sync/atomic/doc_64.test.ts +5 -5
  246. package/gs/sync/atomic/type.gs.ts +28 -28
  247. package/gs/sync/sync.test.ts +109 -1
  248. package/gs/sync/sync.ts +46 -12
  249. package/gs/syscall/fs.ts +8 -8
  250. package/gs/syscall/net.test.ts +1 -1
  251. package/gs/time/parse.test.ts +45 -0
  252. package/gs/time/time.test.ts +46 -23
  253. package/gs/time/time.ts +69 -66
  254. package/gs/unicode/gen.go +198 -0
  255. package/gs/unicode/tables.ts +646 -0
  256. package/gs/unicode/unicode.test.ts +69 -0
  257. package/gs/unicode/unicode.ts +396 -312
  258. package/package.json +2 -2
  259. package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +0 -20
  260. package/dist/gs/github.com/aperturerobotics/util/conc/index.js +0 -134
  261. package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +0 -1
  262. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +0 -30
  263. package/gs/github.com/aperturerobotics/util/conc/index.ts +0 -172
  264. package/gs/github.com/aperturerobotics/util/conc/meta.json +0 -9
@@ -43,6 +43,28 @@ export class SyntaxError extends jsonError {
43
43
  }
44
44
  }
45
45
 
46
+ // Register *json.SyntaxError so a Go `err.(*json.SyntaxError)` assertion in
47
+ // compiled code resolves to this class and exposes the Offset field.
48
+ $.registerStructType(
49
+ 'json.SyntaxError',
50
+ new SyntaxError(),
51
+ [
52
+ {
53
+ name: 'Error',
54
+ args: [],
55
+ returns: [{ type: { kind: $.TypeKind.Basic, name: 'string' } }],
56
+ },
57
+ ],
58
+ SyntaxError,
59
+ [
60
+ {
61
+ name: 'Offset',
62
+ key: 'Offset',
63
+ type: { kind: $.TypeKind.Basic, name: 'int64' },
64
+ },
65
+ ],
66
+ )
67
+
46
68
  export class InvalidUTF8Error extends jsonError {
47
69
  public S: string
48
70
 
@@ -145,23 +167,53 @@ export class Decoder {
145
167
  private disallowUnknownFields = false
146
168
  private inputOffset = 0
147
169
  private useNumber = false
170
+ // buf holds the fully buffered input; pos is the index of the next unread
171
+ // character, so successive Decode/Token calls consume one value at a time and
172
+ // leave the remainder buffered, like Go's streaming Decoder.
173
+ private buf = ''
174
+ private pos = 0
175
+ private filled = false
176
+ private readErr: $.GoError = null
148
177
 
149
178
  public constructor(private readonly reader: io.Reader) {}
150
179
 
151
- public Decode(v: unknown): $.GoError {
180
+ private fill(): void {
181
+ if (this.filled) {
182
+ return
183
+ }
184
+ this.filled = true
152
185
  const [data, err] = readAllSync(this.reader)
153
186
  if (err !== null) {
154
- return err
187
+ this.readErr = err
188
+ return
155
189
  }
156
- this.inputOffset += $.len(data)
157
- return decode(data, v, {
190
+ this.buf = $.bytesToString(data)
191
+ }
192
+
193
+ public Decode(v: unknown): $.GoError {
194
+ this.fill()
195
+ this.pos = skipJSONWhitespace(this.buf, this.pos)
196
+ if (this.pos >= this.buf.length) {
197
+ return this.readErr ?? $.newError('EOF')
198
+ }
199
+ let end: number
200
+ try {
201
+ ;[, end] = scanJSONValue(this.buf, this.pos, this.useNumber)
202
+ } catch (err) {
203
+ return goError(err)
204
+ }
205
+ const raw = $.stringToBytes(this.buf.slice(this.pos, end))
206
+ this.pos = end
207
+ this.inputOffset = jsonByteOffset(this.buf, end)
208
+ return decode(raw, v, {
158
209
  disallowUnknownFields: this.disallowUnknownFields,
159
210
  useNumber: this.useNumber,
160
211
  })
161
212
  }
162
213
 
163
214
  public Buffered(): io.Reader {
164
- return new bytes.Buffer()
215
+ this.fill()
216
+ return bytes.NewBufferString(this.buf.slice(this.pos))!
165
217
  }
166
218
 
167
219
  public DisallowUnknownFields(): void {
@@ -173,11 +225,46 @@ export class Decoder {
173
225
  }
174
226
 
175
227
  public More(): boolean {
176
- return false
228
+ this.fill()
229
+ const next = skipJSONWhitespace(this.buf, this.pos)
230
+ if (next >= this.buf.length) {
231
+ return false
232
+ }
233
+ const c = this.buf[next]
234
+ return c !== ']' && c !== '}'
177
235
  }
178
236
 
179
237
  public Token(): [Token, $.GoError] {
180
- return [null, $.newError('json: token streaming is unsupported')]
238
+ this.fill()
239
+ let cursor = skipJSONWhitespace(this.buf, this.pos)
240
+ // Object/array separators are implicit in Go's token stream; skip them.
241
+ while (cursor < this.buf.length) {
242
+ const sep = this.buf[cursor]
243
+ if (sep === ',' || sep === ':') {
244
+ cursor++
245
+ cursor = skipJSONWhitespace(this.buf, cursor)
246
+ continue
247
+ }
248
+ break
249
+ }
250
+ this.pos = cursor
251
+ if (this.pos >= this.buf.length) {
252
+ return [null, this.readErr ?? $.newError('EOF')]
253
+ }
254
+ const c = this.buf[this.pos]
255
+ if (c === '{' || c === '[' || c === '}' || c === ']') {
256
+ this.pos++
257
+ this.inputOffset = jsonByteOffset(this.buf, this.pos)
258
+ return [c.charCodeAt(0), null]
259
+ }
260
+ try {
261
+ const [value, end] = scanJSONValue(this.buf, this.pos, this.useNumber)
262
+ this.pos = end
263
+ this.inputOffset = jsonByteOffset(this.buf, end)
264
+ return [value, null]
265
+ } catch (err) {
266
+ return [null, goError(err)]
267
+ }
181
268
  }
182
269
 
183
270
  public UseNumber(): void {
@@ -262,6 +349,282 @@ $.registerInterfaceType('json.Unmarshaler', null, [
262
349
  },
263
350
  ])
264
351
 
352
+ // RawJSON wraps a pre-serialized JSON fragment (from a Marshaler or RawMessage)
353
+ // so the encoder can emit its exact token spelling instead of round-tripping it
354
+ // through JSON.parse/stringify, which would normalize numbers like 1e+00 to 1.
355
+ class RawJSON {
356
+ constructor(public raw: string) {}
357
+ }
358
+
359
+ // stripJSONWhitespace removes insignificant whitespace from a JSON document
360
+ // while leaving string contents and every number/literal token byte-for-byte
361
+ // intact, matching Go's json.Compact.
362
+ function stripJSONWhitespace(text: string): string {
363
+ let out = ''
364
+ let inStr = false
365
+ for (let i = 0; i < text.length; i++) {
366
+ const c = text[i]
367
+ if (inStr) {
368
+ out += c
369
+ if (c === '\\') {
370
+ out += text[++i] ?? ''
371
+ } else if (c === '"') {
372
+ inStr = false
373
+ }
374
+ continue
375
+ }
376
+ if (c === '"') {
377
+ inStr = true
378
+ out += c
379
+ continue
380
+ }
381
+ if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
382
+ continue
383
+ }
384
+ out += c
385
+ }
386
+ return out
387
+ }
388
+
389
+ // indentJSON pretty-prints a compact JSON document, preserving every token
390
+ // spelling, matching Go's json.Indent layout: a space after a colon, newlines
391
+ // with prefix + indent*depth after structural characters, and empty {} / []
392
+ // left inline.
393
+ function indentJSON(compact: string, prefix: string, indent: string): string {
394
+ let out = ''
395
+ let depth = 0
396
+ let inStr = false
397
+ const newline = (d: number) => '\n' + prefix + indent.repeat(d)
398
+ for (let i = 0; i < compact.length; i++) {
399
+ const c = compact[i]
400
+ if (inStr) {
401
+ out += c
402
+ if (c === '\\') {
403
+ out += compact[++i] ?? ''
404
+ } else if (c === '"') {
405
+ inStr = false
406
+ }
407
+ continue
408
+ }
409
+ switch (c) {
410
+ case '"':
411
+ inStr = true
412
+ out += c
413
+ break
414
+ case '{':
415
+ case '[':
416
+ if (compact[i + 1] === '}' || compact[i + 1] === ']') {
417
+ out += c
418
+ } else {
419
+ depth++
420
+ out += c + newline(depth)
421
+ }
422
+ break
423
+ case '}':
424
+ case ']':
425
+ if (compact[i - 1] === '{' || compact[i - 1] === '[') {
426
+ out += c
427
+ } else {
428
+ depth--
429
+ out += newline(depth) + c
430
+ }
431
+ break
432
+ case ',':
433
+ out += c + newline(depth)
434
+ break
435
+ case ':':
436
+ out += ': '
437
+ break
438
+ default:
439
+ out += c
440
+ }
441
+ }
442
+ return out
443
+ }
444
+
445
+ // encodeJSON serializes a marshal-prepared value to compact JSON. RawJSON
446
+ // fragments are emitted verbatim; all other values use plain token forms. The
447
+ // caller applies HTML escaping and indentation afterward, matching Go's
448
+ // Marshal-then-indent pipeline.
449
+ function encodeJSON(v: unknown): string {
450
+ if (v instanceof RawJSON) {
451
+ return v.raw
452
+ }
453
+ if (v === null || v === undefined) {
454
+ return 'null'
455
+ }
456
+ const t = typeof v
457
+ if (t === 'boolean') {
458
+ return v ? 'true' : 'false'
459
+ }
460
+ if (t === 'number') {
461
+ return JSON.stringify(v)
462
+ }
463
+ if (t === 'bigint') {
464
+ return (v as bigint).toString()
465
+ }
466
+ if (t === 'string') {
467
+ return JSON.stringify(v)
468
+ }
469
+ if (Array.isArray(v)) {
470
+ return '[' + v.map(encodeJSON).join(',') + ']'
471
+ }
472
+ if (t === 'object') {
473
+ const parts: string[] = []
474
+ for (const [key, value] of Object.entries(v as Record<string, unknown>)) {
475
+ parts.push(JSON.stringify(key) + ':' + encodeJSON(value))
476
+ }
477
+ return '{' + parts.join(',') + '}'
478
+ }
479
+ return JSON.stringify(v)
480
+ }
481
+
482
+ const JSON_WHITESPACE = ' \t\n\r'
483
+
484
+ // jsonByteOffset returns the Go byte offset (UTF-8) of character index i, so a
485
+ // SyntaxError reports the same Offset Go would for multibyte input.
486
+ function jsonByteOffset(text: string, i: number): number {
487
+ return $.len($.stringToBytes(text.slice(0, i)))
488
+ }
489
+
490
+ function jsonSyntaxError(text: string, i: number, msg: string): SyntaxError {
491
+ return new SyntaxError({ Offset: jsonByteOffset(text, i), Message: msg })
492
+ }
493
+
494
+ function skipJSONWhitespace(text: string, i: number): number {
495
+ while (i < text.length && JSON_WHITESPACE.includes(text[i])) {
496
+ i++
497
+ }
498
+ return i
499
+ }
500
+
501
+ // scanJSONValue parses one JSON value beginning at the first non-whitespace
502
+ // character at or after start, returning the decoded value and the index just
503
+ // past it. Numbers decode to JS numbers, or to their exact json.Number source
504
+ // literal when useNumber is set. Malformed input throws a SyntaxError whose
505
+ // Offset is the Go byte offset of the offending character.
506
+ function scanJSONValue(
507
+ text: string,
508
+ start: number,
509
+ useNumber: boolean,
510
+ ): [unknown, number] {
511
+ let i = start
512
+ const skipWs = () => {
513
+ i = skipJSONWhitespace(text, i)
514
+ }
515
+ const fail = (msg = 'invalid character'): never => {
516
+ // Go's Offset counts bytes read up to and including the offending byte.
517
+ const at = Math.min(i + 1, text.length)
518
+ throw jsonSyntaxError(text, at, msg)
519
+ }
520
+ const parseString = (): string => {
521
+ let raw = text[i++] // opening quote
522
+ while (i < text.length) {
523
+ const c = text[i++]
524
+ raw += c
525
+ if (c === '\\') {
526
+ raw += text[i++] ?? ''
527
+ } else if (c === '"') {
528
+ try {
529
+ return JSON.parse(raw) as string
530
+ } catch {
531
+ i -= raw.length
532
+ return fail('invalid string literal')
533
+ }
534
+ }
535
+ }
536
+ return fail('unexpected end of JSON input')
537
+ }
538
+ const parseValue = (): unknown => {
539
+ skipWs()
540
+ if (i >= text.length) {
541
+ return fail('unexpected end of JSON input')
542
+ }
543
+ const c = text[i]
544
+ if (c === '{') {
545
+ i++
546
+ const obj: Record<string, unknown> = {}
547
+ skipWs()
548
+ if (text[i] === '}') {
549
+ i++
550
+ return obj
551
+ }
552
+ for (;;) {
553
+ skipWs()
554
+ if (text[i] !== '"') {
555
+ return fail()
556
+ }
557
+ const key = parseString()
558
+ skipWs()
559
+ if (text[i++] !== ':') {
560
+ i--
561
+ return fail("expected ':' after object key")
562
+ }
563
+ obj[key] = parseValue()
564
+ skipWs()
565
+ const sep = text[i++]
566
+ if (sep === '}') {
567
+ return obj
568
+ }
569
+ if (sep !== ',') {
570
+ i--
571
+ return fail("expected ',' or '}' after object value")
572
+ }
573
+ }
574
+ }
575
+ if (c === '[') {
576
+ i++
577
+ const arr: unknown[] = []
578
+ skipWs()
579
+ if (text[i] === ']') {
580
+ i++
581
+ return arr
582
+ }
583
+ for (;;) {
584
+ arr.push(parseValue())
585
+ skipWs()
586
+ const sep = text[i++]
587
+ if (sep === ']') {
588
+ return arr
589
+ }
590
+ if (sep !== ',') {
591
+ i--
592
+ return fail("expected ',' or ']' after array element")
593
+ }
594
+ }
595
+ }
596
+ if (c === '"') {
597
+ return parseString()
598
+ }
599
+ if (text.startsWith('true', i)) {
600
+ i += 4
601
+ return true
602
+ }
603
+ if (text.startsWith('false', i)) {
604
+ i += 5
605
+ return false
606
+ }
607
+ if (text.startsWith('null', i)) {
608
+ i += 4
609
+ return null
610
+ }
611
+ const numStart = i
612
+ if (c === '-') {
613
+ i++
614
+ }
615
+ while (i < text.length && '0123456789.eE+-'.includes(text[i])) {
616
+ i++
617
+ }
618
+ if (i === numStart) {
619
+ return fail()
620
+ }
621
+ const literal = text.slice(numStart, i)
622
+ return useNumber ? literal : Number(literal)
623
+ }
624
+ const value = parseValue()
625
+ return [value, i]
626
+ }
627
+
265
628
  export function Marshal(v: unknown): [$.Slice<number>, $.GoError] {
266
629
  return marshalBytes(v, '', '', true)
267
630
  }
@@ -273,15 +636,12 @@ function marshalBytes(
273
636
  escapeHTML: boolean,
274
637
  ): [$.Slice<number>, $.GoError] {
275
638
  try {
276
- let text = JSON.stringify(marshalValue(v), null, indent)
639
+ let text = encodeJSON(marshalValue(v))
277
640
  if (escapeHTML) {
278
641
  text = escapeHTMLString(text)
279
642
  }
280
- if (prefix !== '') {
281
- text = text
282
- .split('\n')
283
- .map((line, idx) => (idx === 0 ? line : prefix + line))
284
- .join('\n')
643
+ if (indent !== '' || prefix !== '') {
644
+ text = indentJSON(text, prefix, indent)
285
645
  }
286
646
  return [$.stringToBytes(text), null]
287
647
  } catch (err) {
@@ -294,7 +654,9 @@ export function Compact(
294
654
  src: $.Slice<number>,
295
655
  ): $.GoError {
296
656
  try {
297
- const text = JSON.stringify(JSON.parse($.bytesToString(src)))
657
+ const source = $.bytesToString(src)
658
+ JSON.parse(source) // validate; Go's Compact rejects malformed input
659
+ const text = stripJSONWhitespace(source)
298
660
  const [, err] = $.pointerValue<bytes.Buffer>(dst).Write(
299
661
  $.stringToBytes(text),
300
662
  )
@@ -334,16 +696,14 @@ export function Indent(
334
696
  indent: string,
335
697
  ): $.GoError {
336
698
  try {
337
- const text = JSON.stringify(JSON.parse($.bytesToString(src)), null, indent)
338
- const prefixed =
339
- prefix === '' ? text : (
340
- text
341
- .split('\n')
342
- .map((line, idx) => (idx === 0 ? line : prefix + line))
343
- .join('\n')
344
- )
699
+ const source = $.bytesToString(src)
700
+ JSON.parse(source) // validate; Go's Indent rejects malformed input
701
+ const compact = stripJSONWhitespace(source)
702
+ // Go's Indent copies trailing whitespace after the value verbatim.
703
+ const trailing = source.slice(source.trimEnd().length)
704
+ const text = indentJSON(compact, prefix, indent) + trailing
345
705
  const [, err] = $.pointerValue<bytes.Buffer>(dst).Write(
346
- $.stringToBytes(prefixed),
706
+ $.stringToBytes(text),
347
707
  )
348
708
  return err
349
709
  } catch (err) {
@@ -506,11 +866,13 @@ function marshalValue(v: unknown): unknown {
506
866
  if (err !== null) {
507
867
  throw new MarshalerError({ Err: err })
508
868
  }
869
+ const raw = $.bytesToString(data)
509
870
  try {
510
- return JSON.parse($.bytesToString(data))
871
+ JSON.parse(raw) // validate; preserve exact token spelling below
511
872
  } catch (parseErr) {
512
873
  throw new MarshalerError({ Err: goError(parseErr) })
513
874
  }
875
+ return new RawJSON(stripJSONWhitespace(raw))
514
876
  }
515
877
  if ($.isVarRef(v)) {
516
878
  return marshalValue(v.value)
@@ -565,11 +927,16 @@ function marshalFieldValue(value: unknown, fieldType: unknown): unknown {
565
927
  if (target === null || target === undefined) {
566
928
  return null
567
929
  }
930
+ const raw = $.bytesToString(target as $.Slice<number>)
931
+ if (raw === '') {
932
+ return null
933
+ }
568
934
  try {
569
- return JSON.parse($.bytesToString(target as $.Slice<number>))
935
+ JSON.parse(raw) // validate; preserve exact token spelling below
570
936
  } catch (err) {
571
937
  throw new MarshalerError({ Err: goError(err) })
572
938
  }
939
+ return new RawJSON(stripJSONWhitespace(raw))
573
940
  }
574
941
  return marshalValue(value)
575
942
  }
@@ -610,12 +977,16 @@ function validUnmarshalTarget(value: unknown): boolean {
610
977
 
611
978
  function parseJSON(data: $.Slice<number>, opts: decodeOptions): unknown {
612
979
  const text = $.bytesToString(data)
613
- if (opts.useNumber) {
614
- return JSON.parse(text, (_key, value) =>
615
- typeof value === 'number' ? String(value) : value,
980
+ const [value, end] = scanJSONValue(text, 0, !!opts.useNumber)
981
+ const rest = skipJSONWhitespace(text, end)
982
+ if (rest !== text.length) {
983
+ throw jsonSyntaxError(
984
+ text,
985
+ rest + 1,
986
+ 'invalid character after top-level value',
616
987
  )
617
988
  }
618
- return JSON.parse(text)
989
+ return value
619
990
  }
620
991
 
621
992
  function assignDecodedValue(
@@ -2,7 +2,16 @@ import { describe, expect, it } from 'vitest'
2
2
 
3
3
  import * as $ from '@goscript/builtin/index.js'
4
4
 
5
- import { AsType, Errorf, Is, Join, Wrap, Wrapf } from './errors.js'
5
+ import {
6
+ AsType,
7
+ ErrUnsupported,
8
+ Errorf,
9
+ Is,
10
+ Join,
11
+ New,
12
+ Wrap,
13
+ Wrapf,
14
+ } from './errors.js'
6
15
 
7
16
  class DNSError {
8
17
  public readonly IsNotFound = true
@@ -83,3 +92,37 @@ describe('errors github.com/pkg/errors compatibility helpers', () => {
83
92
  expect(Wrapf(null, 'context %d', 7)).toBe(null)
84
93
  })
85
94
  })
95
+
96
+ describe('errors.Is identity semantics', () => {
97
+ it('does not match distinct errors with equal text', () => {
98
+ expect(Is(New('boom'), New('boom'))).toBe(false)
99
+ })
100
+
101
+ it('does not match ErrUnsupported by message text', () => {
102
+ expect(Is(New('unsupported operation'), ErrUnsupported)).toBe(false)
103
+ })
104
+
105
+ it('matches the same error value', () => {
106
+ const e = New('boom')
107
+ expect(Is(e, e)).toBe(true)
108
+ })
109
+
110
+ it('finds a target in any Join position depth-first', () => {
111
+ const a = New('a')
112
+ const b = New('b')
113
+ expect(Is(Join(a, b), b)).toBe(true)
114
+ expect(Is(Join(a, b), a)).toBe(true)
115
+ expect(Is(Join(a, b), New('b'))).toBe(false)
116
+ })
117
+
118
+ it('matches a wrapped sentinel through Wrap', () => {
119
+ expect(Is(Wrap(ErrUnsupported, 'ctx'), ErrUnsupported)).toBe(true)
120
+ })
121
+
122
+ it('finds a typed error in a later Join position via AsType', () => {
123
+ const dns = $.interfaceValue<$.GoError>(new DNSError(), '*net.DNSError')
124
+ const [matched, ok] = AsType(dnsTypeArgs, Join(New('first'), dns))
125
+ expect(ok).toBe(true)
126
+ expect(matched).toBe(dns)
127
+ })
128
+ })
@@ -123,42 +123,32 @@ export function Is(err: $.GoError, target: $.GoError): boolean {
123
123
  return false
124
124
  }
125
125
 
126
- // Check direct equality
126
+ // Match by identity only. Go's errors.Is never matches by message text:
127
+ // two distinct errors.New values with the same string are not equal.
127
128
  if (err === target) {
128
129
  return true
129
130
  }
130
131
 
131
- // Check if error messages are the same
132
- if (err.Error() === target.Error()) {
133
- return true
134
- }
135
-
136
- // Check if err has an Is method
132
+ // An error may declare equivalence to target via an Is method.
137
133
  if (typeof (err as any).Is === 'function') {
138
134
  if ((err as any).Is(target)) {
139
135
  return true
140
136
  }
141
137
  }
142
138
 
143
- // Recursively check wrapped errors
144
- const unwrapped = Unwrap(err)
145
- if (unwrapped !== null) {
146
- return Is(unwrapped, target)
147
- }
148
-
149
- // Handle multiple wrapped errors
139
+ // Depth-first traversal of the wrap tree. A single-error Unwrap recurses into
140
+ // its child; an Unwrap returning []error (errors.Join) visits every child, so
141
+ // a target in any position matches, not just the first.
150
142
  if (typeof (err as any).Unwrap === 'function') {
151
143
  const result = (err as any).Unwrap()
152
144
  if (Array.isArray(result)) {
153
145
  for (const wrappedErr of result) {
154
- if (
155
- wrappedErr &&
156
- typeof wrappedErr.Error === 'function' &&
157
- Is(wrappedErr, target)
158
- ) {
146
+ if (wrappedErr !== null && Is(wrappedErr, target)) {
159
147
  return true
160
148
  }
161
149
  }
150
+ } else if (result !== null && typeof result?.Error === 'function') {
151
+ return Is(result, target)
162
152
  }
163
153
  }
164
154
 
@@ -202,25 +192,19 @@ export function As(err: $.GoError, target: any): boolean {
202
192
  }
203
193
  }
204
194
 
205
- // Recursively check wrapped errors
206
- const unwrapped = Unwrap(err)
207
- if (unwrapped !== null) {
208
- return As(unwrapped, target)
209
- }
210
-
211
- // Handle multiple wrapped errors
195
+ // Depth-first traversal of the wrap tree, matching errors.Is: a single-error
196
+ // Unwrap recurses into its child; an Unwrap returning []error visits every
197
+ // child so a match in any position is found.
212
198
  if (typeof (err as any).Unwrap === 'function') {
213
199
  const result = (err as any).Unwrap()
214
200
  if (Array.isArray(result)) {
215
201
  for (const wrappedErr of result) {
216
- if (
217
- wrappedErr &&
218
- typeof wrappedErr.Error === 'function' &&
219
- As(wrappedErr, target)
220
- ) {
202
+ if (wrappedErr !== null && As(wrappedErr, target)) {
221
203
  return true
222
204
  }
223
205
  }
206
+ } else if (result !== null && typeof result?.Error === 'function') {
207
+ return As(result, target)
224
208
  }
225
209
  }
226
210