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
@@ -1,6 +1,7 @@
1
1
  import { afterEach, describe, expect, it, vi } from 'vitest'
2
2
  import { resetHostRuntimeForTests } from '@goscript/builtin/hostio.js'
3
3
  import * as $ from '@goscript/builtin/index.js'
4
+ import * as errors from '@goscript/errors/index.js'
4
5
  import * as os from '@goscript/os/index.js'
5
6
  import * as fmt from './fmt.js'
6
7
 
@@ -86,6 +87,21 @@ describe('fmt basic value formatting', () => {
86
87
  expect(fmt.Sprintf('%q', 97)).toBe(JSON.stringify('a'))
87
88
  })
88
89
 
90
+ it('%c encodes runes including astral planes and invalid code points', () => {
91
+ expect(fmt.Sprintf('%c', 0x597d)).toBe('好')
92
+ // U+1F600 is above U+FFFF; fromCharCode would have truncated it.
93
+ expect(fmt.Sprintf('%c', 0x1f600)).toBe('😀')
94
+ expect(fmt.Sprintf('%c', 0x1f600).codePointAt(0)).toBe(0x1f600)
95
+ // Invalid code points map to U+FFFD instead of throwing.
96
+ expect(fmt.Sprintf('%c', 0x110000)).toBe('�')
97
+ expect(fmt.Sprintf('%c', -1)).toBe('�')
98
+ })
99
+
100
+ it('%q on an astral rune does not throw and round-trips the code point', () => {
101
+ expect(fmt.Sprintf('%q', 0x1f600)).toBe(JSON.stringify('😀'))
102
+ expect(fmt.Sprintf('%q', 0x110000)).toBe(JSON.stringify('�'))
103
+ })
104
+
89
105
  it('%p pointer-ish formatting fallback', () => {
90
106
  expect(fmt.Sprintf('%p', {})).toBe('0x0')
91
107
  expect(fmt.Sprintf('%p', { __address: 255 })).toBe('0xff')
@@ -127,8 +143,33 @@ describe('fmt basic value formatting', () => {
127
143
  return '<go stringer>'
128
144
  },
129
145
  }
130
- // We prefer GoString() first
131
- expect(fmt.Sprintf('%v', goStringer)).toBe('<go stringer>')
146
+ // Go consults GoString only for %#v, never plain %v / Sprint.
147
+ expect(fmt.Sprintf('%#v', goStringer)).toBe('<go stringer>')
148
+ expect(fmt.Sprintf('%v', goStringer)).not.toBe('<go stringer>')
149
+ expect(fmt.Sprint(goStringer)).not.toBe('<go stringer>')
150
+ })
151
+
152
+ it('applies # base prefixes and +/space sign flags (Go parity)', () => {
153
+ expect(fmt.Sprintf('%#x', 15)).toBe('0xf')
154
+ expect(fmt.Sprintf('%#X', 15)).toBe('0XF')
155
+ expect(fmt.Sprintf('%#o', 15)).toBe('017')
156
+ expect(fmt.Sprintf('%#x', 0)).toBe('0x0')
157
+ expect(fmt.Sprintf('%+d', 15)).toBe('+15')
158
+ expect(fmt.Sprintf('% d', 15)).toBe(' 15')
159
+ expect(fmt.Sprintf('%+d', -15)).toBe('-15')
160
+ })
161
+
162
+ it('%v prefers Stringer while %#v prefers GoStringer', () => {
163
+ const g = {
164
+ String() {
165
+ return 'G(7)'
166
+ },
167
+ GoString() {
168
+ return 'main.G{n:7}'
169
+ },
170
+ }
171
+ expect(fmt.Sprintf('%v|%#v', g, g)).toBe('G(7)|main.G{n:7}')
172
+ expect(fmt.Sprint(g)).toBe('G(7)')
132
173
  })
133
174
 
134
175
  it('%w formats errors by Error method', () => {
@@ -136,6 +177,21 @@ describe('fmt basic value formatting', () => {
136
177
  expect(fmt.Errorf('wrap: %w', err)?.Error()).toBe('wrap: root')
137
178
  })
138
179
 
180
+ it('%w wraps so Unwrap and errors.Is reach the operand', () => {
181
+ const base = $.newError('root')
182
+ const wrapped = fmt.Errorf('ctx: %w', base)
183
+ expect((wrapped as any).Unwrap()).toBe(base)
184
+ expect(errors.Is(wrapped, base)).toBe(true)
185
+ })
186
+
187
+ it('multiple %w unwrap to every operand', () => {
188
+ const a = $.newError('a')
189
+ const b = $.newError('b')
190
+ const wrapped = fmt.Errorf('%w and %w', a, b)
191
+ expect(errors.Is(wrapped, a)).toBe(true)
192
+ expect(errors.Is(wrapped, b)).toBe(true)
193
+ })
194
+
139
195
  it('%s formats stringers by String method', () => {
140
196
  const stringer = {
141
197
  String() {
@@ -297,4 +353,16 @@ describe('fmt scanning', () => {
297
353
  expect(start.value).toBe(12)
298
354
  expect(end.value).toBe(34)
299
355
  })
356
+
357
+ it('allows trailing input after the matched verbs (Go parity)', () => {
358
+ const v = $.varRef(0)
359
+ const [n, err] = fmt.Sscanf(
360
+ '123 trailing',
361
+ '%d',
362
+ $.interfaceValue(v, '*int64'),
363
+ )
364
+ expect(err).toBeNull()
365
+ expect(n).toBe(1)
366
+ expect(v.value).toBe(123)
367
+ })
300
368
  })
package/gs/fmt/fmt.ts CHANGED
@@ -25,19 +25,58 @@ export interface State {
25
25
  Write(b: $.Bytes): [number, $.GoError | null]
26
26
  }
27
27
 
28
+ // formatInt renders an integer value in the given radix, applying Go's # flag
29
+ // (0x/0X/0 base prefix) and +/space sign flags. The sign precedes the prefix,
30
+ // matching fmt's "%+#x" ordering.
31
+ function formatInt(
32
+ value: any,
33
+ radix: number,
34
+ flags: string,
35
+ upper: boolean,
36
+ ): string {
37
+ const big = typeof value === 'bigint'
38
+ const neg = big ? (value as bigint) < 0n : Number(value) < 0
39
+ let digits = big
40
+ ? (neg ? -(value as bigint) : (value as bigint)).toString(radix)
41
+ : Math.abs(Math.trunc(Number(value))).toString(radix)
42
+ if (upper) {
43
+ digits = digits.toUpperCase()
44
+ }
45
+ let prefix = ''
46
+ if (flags.includes('#')) {
47
+ if (radix === 16) {
48
+ prefix = upper ? '0X' : '0x'
49
+ } else if (radix === 8 && !digits.startsWith('0')) {
50
+ prefix = '0'
51
+ }
52
+ }
53
+ let sign = ''
54
+ if (neg) {
55
+ sign = '-'
56
+ } else if (flags.includes('+')) {
57
+ sign = '+'
58
+ } else if (flags.includes(' ')) {
59
+ sign = ' '
60
+ }
61
+ return sign + prefix + digits
62
+ }
63
+
28
64
  // Simple printf-style formatting implementation
29
- function formatValue(value: any, verb: string): string {
65
+ function formatValue(value: any, verb: string, flags = ''): string {
30
66
  if (value === null || value === undefined) {
31
67
  return '<nil>'
32
68
  }
33
69
 
34
70
  switch (verb) {
35
71
  case 'v': // default format
72
+ if (flags.includes('#') && hasGoString(value)) {
73
+ return defaultFormat(value.GoString())
74
+ }
36
75
  return defaultFormat(value)
37
76
  case 'w': // wrapped error
38
77
  return defaultFormat(value)
39
78
  case 'd': // decimal integer
40
- return String(Math.trunc(Number(value)))
79
+ return formatInt(value, 10, flags, false)
41
80
  case 'f': // decimal point, no exponent
42
81
  return Number(value).toString()
43
82
  case 's': // string
@@ -55,15 +94,18 @@ function formatValue(value: any, verb: string): string {
55
94
  if (typeof value === 'string') return 'string'
56
95
  return typeof value
57
96
  case 'c': // character (Unicode code point)
58
- return String.fromCharCode(Number(value))
97
+ // Go's %c encodes the rune as a Unicode character; fromCharCode truncates
98
+ // any code point above U+FFFF and mangles surrogates. runeToString owns
99
+ // the Go string(rune) rule (astral intact, invalid -> U+FFFD, no throw).
100
+ return $.runeToString(Number(value))
59
101
  case 'x': // hexadecimal lowercase
60
- return Number(value).toString(16)
102
+ return formatInt(value, 16, flags, false)
61
103
  case 'X': // hexadecimal uppercase
62
- return Number(value).toString(16).toUpperCase()
104
+ return formatInt(value, 16, flags, true)
63
105
  case 'o': // octal
64
- return Number(value).toString(8)
106
+ return formatInt(value, 8, flags, false)
65
107
  case 'b': // binary
66
- return Number(value).toString(2)
108
+ return formatInt(value, 2, flags, false)
67
109
  case 'e': // scientific notation lowercase
68
110
  return Number(value).toExponential()
69
111
  case 'E': // scientific notation uppercase
@@ -74,9 +116,9 @@ function formatValue(value: any, verb: string): string {
74
116
  return Number(value).toPrecision().toUpperCase()
75
117
  case 'q': // quoted string / rune
76
118
  if (typeof value === 'number' && Number.isInteger(value)) {
77
- // emulate quoted rune for integers in basic range
78
- const ch = String.fromCodePoint(value)
79
- return JSON.stringify(ch)
119
+ // emulate quoted rune; runeToString avoids fromCodePoint throwing on
120
+ // invalid code points and maps them to U+FFFD as Go's %q does.
121
+ return JSON.stringify($.runeToString(value))
80
122
  }
81
123
  return JSON.stringify(String(value))
82
124
  case 'p': {
@@ -98,6 +140,16 @@ function hasGoTypeName(value: unknown): value is { __goType: string } {
98
140
  )
99
141
  }
100
142
 
143
+ // hasGoString reports whether value implements the GoStringer interface. Go
144
+ // invokes GoString only for the %#v verb, never for %v, %s, or Sprint.
145
+ function hasGoString(value: unknown): value is { GoString: () => string } {
146
+ return (
147
+ value !== null &&
148
+ typeof value === 'object' &&
149
+ typeof (value as { GoString?: unknown }).GoString === 'function'
150
+ )
151
+ }
152
+
101
153
  type MaybeString = string | PromiseLike<string>
102
154
 
103
155
  function isPromiseLike<T = unknown>(value: unknown): value is PromiseLike<T> {
@@ -145,17 +197,9 @@ function defaultFormatMaybe(value: any): MaybeString {
145
197
  if (Array.isArray(value))
146
198
  return joinMaybe(value.map(defaultFormatMaybe), ' ', '[', ']')
147
199
  if (typeof value === 'object') {
148
- // Prefer GoStringer if present
149
- if (
150
- (value as any).GoString &&
151
- typeof (value as any).GoString === 'function'
152
- ) {
153
- try {
154
- return toMaybeString((value as any).GoString())
155
- } catch {
156
- // Ignore error by continuing to next case.
157
- }
158
- }
200
+ // GoStringer is intentionally not consulted here: Go calls GoString only
201
+ // for the %#v verb, which formatValue handles before reaching this default
202
+ // path. %v, %s, Sprint, and Print use Error then Stringer.
159
203
  // Prefer error interface if present
160
204
  if ((value as any).Error && typeof (value as any).Error === 'function') {
161
205
  try {
@@ -233,18 +277,21 @@ function parseFormat(format: string, args: any[]): string {
233
277
  return formatted
234
278
  }
235
279
 
236
- function formatValueMaybe(value: any, verb: string): MaybeString {
280
+ function formatValueMaybe(value: any, verb: string, flags = ''): MaybeString {
237
281
  switch (verb) {
238
282
  case 'v':
283
+ if (flags.includes('#') && hasGoString(value)) {
284
+ return toMaybeString(value.GoString())
285
+ }
286
+ return defaultFormatMaybe(value)
239
287
  case 'w':
288
+ return defaultFormatMaybe(value)
240
289
  case 's':
241
- if (verb === 's') {
242
- if (typeof value === 'string') return value
243
- if (value instanceof Uint8Array) return $.bytesToString(value)
244
- }
290
+ if (typeof value === 'string') return value
291
+ if (value instanceof Uint8Array) return $.bytesToString(value)
245
292
  return defaultFormatMaybe(value)
246
293
  default:
247
- return formatValue(value, verb)
294
+ return formatValue(value, verb, flags)
248
295
  }
249
296
  }
250
297
 
@@ -351,8 +398,8 @@ function parseFormatMaybe(
351
398
  if (formatted === null) {
352
399
  formatted =
353
400
  allowAsync ?
354
- formatValueMaybe(arg, verb)
355
- : formatValue(arg, verb)
401
+ formatValueMaybe(arg, verb, flags)
402
+ : formatValue(arg, verb, flags)
356
403
  }
357
404
 
358
405
  parts.push(
@@ -591,7 +638,52 @@ export function Appendln(b: $.Bytes, ...a: any[]): $.Bytes {
591
638
  // Error creation
592
639
  export function Errorf(format: string, ...a: any[]): any {
593
640
  const message = parseFormat(format, a)
594
- return errors.New(message)
641
+ const err = errors.New(message)
642
+ // %w operands are wrapped: the result must Unwrap to them so errors.Is/As can
643
+ // walk the chain. One %w unwraps to a single error; multiple %w (Go 1.20+)
644
+ // unwrap to an []error, matching the depth-first traversal in errors.Is.
645
+ const wrapped = errorfWrappedArgs(format, a)
646
+ if (wrapped.length === 1) {
647
+ ;(err as any).Unwrap = (): $.GoError => wrapped[0]
648
+ } else if (wrapped.length > 1) {
649
+ ;(err as any).Unwrap = (): $.GoError[] => wrapped
650
+ }
651
+ return err
652
+ }
653
+
654
+ // errorfWrappedArgs returns the error operands consumed by %w verbs, in order.
655
+ // It walks the format string with the same flag/width/precision/verb skip and
656
+ // the same positional arg consumption as parseFormat, so the operand a %w binds
657
+ // to here is exactly the one it formatted in the message.
658
+ function errorfWrappedArgs(format: string, args: any[]): $.GoError[] {
659
+ const wrapped: $.GoError[] = []
660
+ let argIndex = 0
661
+ for (let i = 0; i < format.length; i++) {
662
+ if (format[i] !== '%' || i + 1 >= format.length) {
663
+ continue
664
+ }
665
+ if (format[i + 1] === '%') {
666
+ i++
667
+ continue
668
+ }
669
+ let j = i + 1
670
+ while (j < format.length && '+-# 0'.includes(format[j])) j++
671
+ while (j < format.length && format[j] >= '0' && format[j] <= '9') j++
672
+ if (j < format.length && format[j] === '.') {
673
+ j++
674
+ while (j < format.length && format[j] >= '0' && format[j] <= '9') j++
675
+ }
676
+ if (j < format.length) {
677
+ if (argIndex < args.length) {
678
+ if (format[j] === 'w') {
679
+ wrapped.push(args[argIndex] as $.GoError)
680
+ }
681
+ argIndex++
682
+ }
683
+ i = j
684
+ }
685
+ }
686
+ return wrapped
595
687
  }
596
688
 
597
689
  // FormatString - simplified implementation
@@ -676,12 +768,15 @@ export function Sscanf(
676
768
  function buildScanPattern(
677
769
  format: string,
678
770
  ): { pattern: RegExp; verbs: string[] } | null {
771
+ // Anchor at the start only: Go's Sscanf consumes a prefix and allows trailing
772
+ // input to remain, so no end anchor. A run of whitespace in the format matches
773
+ // spaces and tabs but not a newline, which Go treats as a record boundary.
679
774
  let source = '^'
680
775
  const verbs: string[] = []
681
776
  for (let i = 0; i < format.length; i++) {
682
777
  const ch = format[i]
683
778
  if (ch !== '%') {
684
- source += /\s/.test(ch) ? '\\s+' : escapeRegExp(ch)
779
+ source += /\s/.test(ch) ? '[ \\t]+' : escapeRegExp(ch)
685
780
  continue
686
781
  }
687
782
  const verb = format[++i]
@@ -690,18 +785,17 @@ function buildScanPattern(
690
785
  continue
691
786
  }
692
787
  if (verb === 'd') {
693
- source += '([+-]?\\d+)'
788
+ source += '\\s*([+-]?\\d+)' // verbs skip leading whitespace, incl. newlines
694
789
  verbs.push(verb)
695
790
  continue
696
791
  }
697
792
  if (verb === 's') {
698
- source += '(\\S+)'
793
+ source += '\\s*(\\S+)'
699
794
  verbs.push(verb)
700
795
  continue
701
796
  }
702
797
  return null
703
798
  }
704
- source += '$'
705
799
  return { pattern: new RegExp(source), verbs }
706
800
  }
707
801
 
@@ -1378,7 +1378,7 @@ function makeTime(
1378
1378
  const millis =
1379
1379
  globalThis.Date.UTC(year, month - 1, day, hour, minute, second) -
1380
1380
  offsetMinutes * 60 * 1000
1381
- return time.Unix(Math.floor(millis / 1000), 0)
1381
+ return time.Unix(BigInt(Math.floor(millis / 1000)), 0n)
1382
1382
  }
1383
1383
 
1384
1384
  function parseGeneralizedTime(value: string): time.Time | null {
@@ -1,6 +1,11 @@
1
1
  import { describe, expect, test } from 'vitest'
2
2
 
3
- import { CountString, IndexByteString, LastIndexByteString } from './index.js'
3
+ import {
4
+ CountString,
5
+ IndexByteString,
6
+ IndexString,
7
+ LastIndexByteString,
8
+ } from './index.js'
4
9
 
5
10
  describe('internal/bytealg string byte indexes', () => {
6
11
  test('finds first and last byte positions in strings', () => {
@@ -15,4 +20,24 @@ describe('internal/bytealg string byte indexes', () => {
15
20
  expect(CountString('hello', 'x'.charCodeAt(0))).toBe(0)
16
21
  expect(CountString('', 'x'.charCodeAt(0))).toBe(0)
17
22
  })
23
+
24
+ test('indexes UTF-8 bytes, not UTF-16 code units (Go semantics)', () => {
25
+ // '你' encodes to UTF-8 bytes E4 BD A0. A 0xA0 byte lives at byte index 2,
26
+ // and the trailing 'A' at byte index 3; UTF-16 search would mislocate both.
27
+ const s = '你A'
28
+ expect(IndexByteString(s, 0xe4)).toBe(0)
29
+ expect(IndexByteString(s, 0xa0)).toBe(2)
30
+ expect(IndexByteString(s, 'A'.charCodeAt(0))).toBe(3)
31
+ expect(LastIndexByteString(s, 0xbd)).toBe(1)
32
+ expect(CountString('日本語', 0xe6)).toBe(2)
33
+ })
34
+
35
+ test('IndexString returns the byte offset of a multibyte substring', () => {
36
+ // 'A你B': 'B' is at byte index 4 (1 + 3 UTF-8 bytes for 你).
37
+ expect(IndexString('A你B', 'B')).toBe(4)
38
+ expect(IndexString('A你B', '你')).toBe(1)
39
+ expect(IndexString('hello', '')).toBe(0)
40
+ expect(IndexString('hi', 'hello')).toBe(-1)
41
+ expect(IndexString('hello', 'xyz')).toBe(-1)
42
+ })
18
43
  })
@@ -1,5 +1,7 @@
1
1
  // Placeholder bytealg module for reflect package compatibility
2
2
 
3
+ import * as $ from '@goscript/builtin/index.js'
4
+
3
5
  // Helper function to normalize bytes input
4
6
  function normalizeBytes(b: any): any[] {
5
7
  if (b === null || b === undefined) {
@@ -126,21 +128,35 @@ export function IndexRabinKarp(s: any, sep: any): number {
126
128
  return Index(s, sep)
127
129
  }
128
130
 
131
+ // Go bytealg operates on the UTF-8 byte representation of a string and returns
132
+ // byte indices, not UTF-16 code-unit indices. String.fromCharCode/indexOf would
133
+ // search code units, mislocating any byte >127 or any multi-byte rune. Search
134
+ // the Go bytes via the builtin string owner so indices match Go.
129
135
  export function IndexByteString(s: string, b: number): number {
130
- const char = String.fromCharCode(b)
131
- return s.indexOf(char)
136
+ const bytes = $.stringToBytes(s)
137
+ for (let i = 0; i < bytes.length; i++) {
138
+ if (bytes[i] === b) {
139
+ return i
140
+ }
141
+ }
142
+ return -1
132
143
  }
133
144
 
134
145
  export function LastIndexByteString(s: string, b: number): number {
135
- const char = String.fromCharCode(b)
136
- return s.lastIndexOf(char)
146
+ const bytes = $.stringToBytes(s)
147
+ for (let i = bytes.length - 1; i >= 0; i--) {
148
+ if (bytes[i] === b) {
149
+ return i
150
+ }
151
+ }
152
+ return -1
137
153
  }
138
154
 
139
155
  export function CountString(s: string, b: number): number {
140
- const char = String.fromCharCode(b)
156
+ const bytes = $.stringToBytes(s)
141
157
  let count = 0
142
- for (let i = 0; i < s.length; i++) {
143
- if (s[i] === char) {
158
+ for (let i = 0; i < bytes.length; i++) {
159
+ if (bytes[i] === b) {
144
160
  count++
145
161
  }
146
162
  }
@@ -148,7 +164,27 @@ export function CountString(s: string, b: number): number {
148
164
  }
149
165
 
150
166
  export function IndexString(s: string, substr: string): number {
151
- return s.indexOf(substr)
167
+ const bytes = $.stringToBytes(s)
168
+ const sub = $.stringToBytes(substr)
169
+ if (sub.length === 0) {
170
+ return 0
171
+ }
172
+ if (sub.length > bytes.length) {
173
+ return -1
174
+ }
175
+ for (let i = 0; i <= bytes.length - sub.length; i++) {
176
+ let found = true
177
+ for (let j = 0; j < sub.length; j++) {
178
+ if (bytes[i + j] !== sub[j]) {
179
+ found = false
180
+ break
181
+ }
182
+ }
183
+ if (found) {
184
+ return i
185
+ }
186
+ }
187
+ return -1
152
188
  }
153
189
 
154
190
  export function MakeNoZero(n: number): Uint8Array {
@@ -12,8 +12,9 @@ export function BEUint32(b: $.Bytes): number {
12
12
  return (((b![0] << 24) >>> 0) | (b![1] << 16) | (b![2] << 8) | b![3]) >>> 0
13
13
  }
14
14
 
15
- export function BEUint64(b: $.Bytes): number {
16
- return $.uint(
15
+ export function BEUint64(b: $.Bytes): bigint {
16
+ return BigInt.asUintN(
17
+ 64,
17
18
  (BigInt(b![0]) << 56n) |
18
19
  (BigInt(b![1]) << 48n) |
19
20
  (BigInt(b![2]) << 40n) |
@@ -33,8 +34,9 @@ export function LEUint32(b: $.Bytes): number {
33
34
  return b![0] | (b![1] << 8) | (b![2] << 16) | (b![3] << 24)
34
35
  }
35
36
 
36
- export function LEUint64(b: $.Bytes): number {
37
- return $.uint(
37
+ export function LEUint64(b: $.Bytes): bigint {
38
+ return BigInt.asUintN(
39
+ 64,
38
40
  BigInt(b![0]) |
39
41
  (BigInt(b![1]) << 8n) |
40
42
  (BigInt(b![2]) << 16n) |
@@ -16,8 +16,8 @@ export function FormatFileInfo(info: FileInfo): string {
16
16
  b.push(' ')
17
17
 
18
18
  let size = info!.Size()
19
- const usize = size >= 0 ? size : -size
20
- if (size < 0) {
19
+ const usize = size >= 0n ? size : -size
20
+ if (size < 0n) {
21
21
  b.push('-')
22
22
  }
23
23
 
package/gs/io/fs/fs.ts CHANGED
@@ -335,7 +335,7 @@ export type FileInfo = null | {
335
335
  // base name of the file
336
336
  Name(): string
337
337
  // length in bytes for regular files; system-dependent for others
338
- Size(): number
338
+ Size(): bigint
339
339
  // underlying data source (can return nil)
340
340
  Sys(): null | any
341
341
  }
@@ -359,7 +359,7 @@ $.registerInterfaceType(
359
359
  {
360
360
  name: 'Size',
361
361
  args: [],
362
- returns: [{ type: { kind: $.TypeKind.Basic, name: 'number' } }],
362
+ returns: [{ type: { kind: $.TypeKind.Basic, name: 'int64' } }],
363
363
  },
364
364
  {
365
365
  name: 'Sys',
@@ -17,8 +17,8 @@ function fileInfo(name: string, isDir = false): FileInfo {
17
17
  Name(): string {
18
18
  return name
19
19
  },
20
- Size(): number {
21
- return 0
20
+ Size(): bigint {
21
+ return 0n
22
22
  },
23
23
  Sys(): null {
24
24
  return null
@@ -17,8 +17,8 @@ function fileInfo(name: string, isDir = false): FileInfo {
17
17
  Name(): string {
18
18
  return name
19
19
  },
20
- Size(): number {
21
- return 0
20
+ Size(): bigint {
21
+ return 0n
22
22
  },
23
23
  Sys(): null {
24
24
  return null
@@ -26,8 +26,8 @@ class info implements FileInfo {
26
26
  return this.name
27
27
  }
28
28
 
29
- Size(): number {
30
- return 0
29
+ Size(): bigint {
30
+ return 0n
31
31
  }
32
32
 
33
33
  Sys(): null {
package/gs/io/io.test.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import * as $ from '@goscript/builtin/index.js'
2
2
  import {
3
+ EOF,
3
4
  LimitedReader,
4
5
  MultiWriter,
5
6
  NewSectionReader,
6
7
  NopCloser,
7
8
  Pipe,
9
+ ReadAll,
8
10
  TeeReader,
9
11
  } from './index.js'
10
12
  import { describe, expect, test } from 'vitest'
@@ -32,8 +34,8 @@ class captureWriter {
32
34
  class syncReaderAt {
33
35
  constructor(private data: Uint8Array) {}
34
36
 
35
- ReadAt(p: $.Bytes, off: number): [number, $.GoError] {
36
- const n = $.copy(p, this.data.subarray(off))
37
+ ReadAt(p: $.Bytes, off: bigint): [number, $.GoError] {
38
+ const n = $.copy(p, this.data.subarray(Number(off)))
37
39
  return [n, n < $.len(p) ? (new Error('EOF') as $.GoError) : null]
38
40
  }
39
41
  }
@@ -42,7 +44,7 @@ describe('io override', () => {
42
44
  test('LimitedReader accepts generated struct-literal construction', async () => {
43
45
  const reader = new LimitedReader({
44
46
  R: new sliceReader($.stringToBytes('abcdef')),
45
- N: 3,
47
+ N: 3n,
46
48
  })
47
49
  const buf = new Uint8Array(8)
48
50
 
@@ -154,10 +156,10 @@ describe('io override', () => {
154
156
  test('SectionReader awaits async ReaderAt', async () => {
155
157
  const reader = NewSectionReader(
156
158
  {
157
- async ReadAt(p: $.Bytes, off: number): Promise<[number, $.GoError]> {
159
+ async ReadAt(p: $.Bytes, off: bigint): Promise<[number, $.GoError]> {
158
160
  await Promise.resolve()
159
161
  const data = $.stringToBytes('abcdef')
160
- const n = $.copy(p, data.subarray(off))
162
+ const n = $.copy(p, data.subarray(Number(off)))
161
163
  return [n, n < $.len(p) ? (new Error('EOF') as $.GoError) : null]
162
164
  },
163
165
  } as any,
@@ -187,4 +189,44 @@ describe('io override', () => {
187
189
  expect(readBytes).toBe(5)
188
190
  expect(Buffer.from(buf).toString('utf8')).toBe('later')
189
191
  })
192
+
193
+ test('ReadAll returns the bytes already read with a non-EOF error', async () => {
194
+ const boom = new Error('boom') as $.GoError
195
+ let called = false
196
+ const reader = {
197
+ Read(p: $.Bytes): [number, $.GoError] {
198
+ if (called) {
199
+ return [0, EOF]
200
+ }
201
+ called = true
202
+ const data = $.stringToBytes('hello')
203
+ p!.set(data, 0)
204
+ return [$.len(data), boom]
205
+ },
206
+ }
207
+
208
+ const [data, err] = await ReadAll(reader)
209
+ expect(err).toBe(boom)
210
+ expect($.bytesToString(data)).toBe('hello')
211
+ })
212
+
213
+ test('ReadAll reports EOF as a nil error', async () => {
214
+ const data = $.stringToBytes('world')
215
+ let offset = 0
216
+ const reader = {
217
+ Read(p: $.Bytes): [number, $.GoError] {
218
+ if (offset >= $.len(data)) {
219
+ return [0, EOF]
220
+ }
221
+ const n = Math.min($.len(p), $.len(data) - offset)
222
+ p!.set((data as Uint8Array).subarray(offset, offset + n), 0)
223
+ offset += n
224
+ return [n, null]
225
+ },
226
+ }
227
+
228
+ const [out, err] = await ReadAll(reader)
229
+ expect(err).toBeNull()
230
+ expect($.bytesToString(out)).toBe('world')
231
+ })
190
232
  })