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,60 +1,174 @@
1
1
  import * as $ from "@goscript/builtin/index.js";
2
- import { ErrSyntax, ErrRange, NumError } from "./atoi.gs.js";
2
+ import { ErrSyntax, ErrRange, NumError, underscoreOK } from "./atoi.gs.js";
3
3
 
4
- // ParseFloat converts the string s to a floating-point number
5
- // with the precision specified by bitSize: 32 for float32, or 64 for float64.
6
- // When bitSize=32, the result still has type float64, but it will be
7
- // convertible to float32 without changing its value.
8
- export function ParseFloat(s: string, bitSize: number): [number, $.GoError] {
4
+ const maxFloat32 = 3.4028234663852886e+38;
5
+
6
+ // special matches Go's special-value parsing: an optional sign followed by a
7
+ // case-insensitive inf/infinity/nan, with nan rejecting a sign. It returns the
8
+ // value and whether s was a special form.
9
+ function special(s: string): [number, boolean] {
9
10
  if (s === "") {
10
- return [0, new NumError({Func: "ParseFloat", Num: s, Err: ErrSyntax})];
11
+ return [0, false];
12
+ }
13
+ let sign = 1;
14
+ let nsign = 0;
15
+ if (s[0] === '+' || s[0] === '-') {
16
+ if (s[0] === '-') {
17
+ sign = -1;
18
+ }
19
+ nsign = 1;
20
+ }
21
+ const body = s.slice(nsign).toLowerCase();
22
+ if (body === "nan") {
23
+ if (nsign === 1) {
24
+ return [0, false]; // signed NaN is not accepted
25
+ }
26
+ return [NaN, true];
27
+ }
28
+ if (body === "inf" || body === "infinity") {
29
+ return [sign * Infinity, true];
30
+ }
31
+ return [0, false];
32
+ }
33
+
34
+ // parseHexFloat parses Go's hexadecimal floating-point syntax
35
+ // (0x1.8p3) into a float64, returning [value, ok]. The exponent letter p and a
36
+ // decimal exponent are mandatory; the value is mantissa * 2^(exp - 4*fracDigits).
37
+ function parseHexFloat(s: string): [number, boolean] {
38
+ let sign = 1;
39
+ let i = 0;
40
+ if (s[0] === '+' || s[0] === '-') {
41
+ if (s[0] === '-') {
42
+ sign = -1;
43
+ }
44
+ i = 1;
45
+ }
46
+ if (i + 1 >= s.length || s[i] !== '0' || s[i + 1].toLowerCase() !== 'x') {
47
+ return [0, false];
48
+ }
49
+ i += 2;
50
+ let intPart = "";
51
+ while (i < s.length && /[0-9a-fA-F]/.test(s[i])) {
52
+ intPart += s[i];
53
+ i++;
54
+ }
55
+ let fracPart = "";
56
+ if (i < s.length && s[i] === '.') {
57
+ i++;
58
+ while (i < s.length && /[0-9a-fA-F]/.test(s[i])) {
59
+ fracPart += s[i];
60
+ i++;
61
+ }
62
+ }
63
+ if (intPart === "" && fracPart === "") {
64
+ return [0, false];
65
+ }
66
+ if (i >= s.length || s[i].toLowerCase() !== 'p') {
67
+ return [0, false]; // exponent is required for hex floats
11
68
  }
69
+ i++;
70
+ let expSign = 1;
71
+ if (i < s.length && (s[i] === '+' || s[i] === '-')) {
72
+ if (s[i] === '-') {
73
+ expSign = -1;
74
+ }
75
+ i++;
76
+ }
77
+ if (i >= s.length) {
78
+ return [0, false];
79
+ }
80
+ let expStr = "";
81
+ while (i < s.length && s[i] >= '0' && s[i] <= '9') {
82
+ expStr += s[i];
83
+ i++;
84
+ }
85
+ if (expStr === "" || i !== s.length) {
86
+ return [0, false];
87
+ }
88
+ const mant = parseInt(intPart + fracPart, 16);
89
+ const exp = expSign * parseInt(expStr, 10) - 4 * fracPart.length;
90
+ return [sign * mant * Math.pow(2, exp), true];
91
+ }
92
+
93
+ // decimalSyntaxOK reports whether s is a syntactically valid Go decimal float
94
+ // literal (after underscores are removed): optional sign, a mantissa with digits
95
+ // on at least one side of an optional dot, and an optional decimal exponent.
96
+ function decimalSyntaxOK(s: string): boolean {
97
+ return /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(s);
98
+ }
12
99
 
13
- // Validate bitSize
14
- if (bitSize !== 32 && bitSize !== 64) {
15
- return [0, new NumError({Func: "ParseFloat", Num: s, Err: ErrSyntax})];
100
+ // hasNonZeroDigit reports whether s contains a digit other than 0, used to tell
101
+ // a true zero from an underflow to zero.
102
+ function hasNonZeroDigit(s: string): boolean {
103
+ for (const ch of s) {
104
+ if (ch >= '1' && ch <= '9') {
105
+ return true;
106
+ }
16
107
  }
108
+ return false;
109
+ }
110
+
111
+ // ParseFloat converts the string s to a floating-point number with the precision
112
+ // specified by bitSize: 32 for float32 or 64 for float64. The result always has
113
+ // type float64; for bitSize 32 it is rounded to float32 precision and reports
114
+ // ErrRange on overflow to infinity or underflow to zero. Any bitSize other than
115
+ // 32 is treated as float64.
116
+ export function ParseFloat(s: string, bitSize: number): [number, $.GoError] {
117
+ const syntax = (): [number, $.GoError] => [0, new NumError({Func: "ParseFloat", Num: s, Err: ErrSyntax})];
118
+ const range = (v: number): [number, $.GoError] => [v, new NumError({Func: "ParseFloat", Num: s, Err: ErrRange})];
17
119
 
18
- // Handle special cases
19
- const lower = s.toLowerCase();
20
- switch (lower) {
21
- case "+inf":
22
- case "inf":
23
- case "+infinity":
24
- case "infinity":
25
- return [Infinity, null];
26
- case "-inf":
27
- case "-infinity":
28
- return [-Infinity, null];
29
- case "nan":
30
- return [NaN, null];
120
+ if (s === "") {
121
+ return syntax();
31
122
  }
32
123
 
33
- // Remove underscores if present (Go allows them in numeric literals)
34
- let cleanS = s;
35
- if (s.includes('_')) {
36
- cleanS = s.replace(/_/g, '');
124
+ const [sv, isSpecial] = special(s);
125
+ if (isSpecial) {
126
+ return [sv, null];
37
127
  }
38
128
 
39
- // Use JavaScript's parseFloat
40
- const result = parseFloat(cleanS);
41
-
42
- if (isNaN(result)) {
43
- return [0, new NumError({Func: "ParseFloat", Num: s, Err: ErrSyntax})];
129
+ let value: number;
130
+ let nonZero: boolean;
131
+ if (/^[+-]?0[xX]/.test(s)) {
132
+ const [hv, ok] = parseHexFloat(s);
133
+ if (!ok) {
134
+ return syntax();
135
+ }
136
+ value = hv;
137
+ nonZero = hv !== 0;
138
+ } else {
139
+ if (s.includes('_')) {
140
+ if (!underscoreOK(s)) {
141
+ return syntax();
142
+ }
143
+ }
144
+ const clean = s.replace(/_/g, '');
145
+ if (!decimalSyntaxOK(clean)) {
146
+ return syntax();
147
+ }
148
+ value = Number(clean);
149
+ nonZero = hasNonZeroDigit(clean);
44
150
  }
45
151
 
46
- // Check for range errors based on bitSize
47
152
  if (bitSize === 32) {
48
- const maxFloat32 = 3.4028234663852886e+38;
49
- const minFloat32 = 1.175494351e-38;
50
-
51
- if (isFinite(result) && Math.abs(result) > maxFloat32) {
52
- return [result > 0 ? Infinity : -Infinity, new NumError({Func: "ParseFloat", Num: s, Err: ErrRange})];
153
+ const f = Math.fround(value);
154
+ if (!isFinite(f) && isFinite(value)) {
155
+ return range(value > 0 ? Infinity : -Infinity);
156
+ }
157
+ if (f === 0 && nonZero) {
158
+ return range(0);
53
159
  }
54
- if (isFinite(result) && result !== 0 && Math.abs(result) < minFloat32) {
55
- return [0, new NumError({Func: "ParseFloat", Num: s, Err: ErrRange})];
160
+ // A finite float64 above float32 max also overflows.
161
+ if (isFinite(value) && Math.abs(value) > maxFloat32) {
162
+ return range(value > 0 ? Infinity : -Infinity);
56
163
  }
164
+ return [f, null];
57
165
  }
58
166
 
59
- return [result, null];
60
- }
167
+ if (!isFinite(value) && nonZero) {
168
+ return range(value > 0 ? Infinity : -Infinity);
169
+ }
170
+ if (value === 0 && nonZero) {
171
+ return range(0);
172
+ }
173
+ return [value, null];
174
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ParseFloat } from './atof.gs.js'
4
+ import { ErrRange, ErrSyntax, NumError } from './atoi.gs.js'
5
+
6
+ // Ground truth captured from go1.26.4 strconv.ParseFloat over the same inputs.
7
+ describe('strconv.ParseFloat (Go float syntax)', () => {
8
+ it('parses decimal floats and forms with a leading dot', () => {
9
+ expect(ParseFloat('1.5', 64)).toEqual([1.5, null])
10
+ expect(ParseFloat('.5', 64)).toEqual([0.5, null])
11
+ expect(ParseFloat('1_000.5', 64)).toEqual([1000.5, null])
12
+ })
13
+
14
+ it('rejects trailing junk and leading whitespace over the whole input', () => {
15
+ const [, errJunk] = ParseFloat('1.5abc', 64)
16
+ expect((errJunk as NumError).Err).toBe(ErrSyntax)
17
+ const [, errWs] = ParseFloat(' 1.5', 64)
18
+ expect((errWs as NumError).Err).toBe(ErrSyntax)
19
+ })
20
+
21
+ it('parses Go hexadecimal floating-point syntax', () => {
22
+ expect(ParseFloat('0x1p2', 64)).toEqual([4, null])
23
+ expect(ParseFloat('0x1.8p3', 64)).toEqual([12, null])
24
+ })
25
+
26
+ it('reports ErrRange on overflow to infinity', () => {
27
+ const [v, err] = ParseFloat('1e400', 64)
28
+ expect(v).toBe(Infinity)
29
+ expect((err as NumError).Err).toBe(ErrRange)
30
+ })
31
+
32
+ it('rounds to float32 precision and ranges float32 overflow', () => {
33
+ expect(ParseFloat('0.1', 32)).toEqual([0.10000000149011612, null])
34
+ const [v, err] = ParseFloat('3.5e38', 32)
35
+ expect(v).toBe(Infinity)
36
+ expect((err as NumError).Err).toBe(ErrRange)
37
+ })
38
+
39
+ it('accepts inf and nan special forms', () => {
40
+ expect(ParseFloat('inf', 64)).toEqual([Infinity, null])
41
+ expect(ParseFloat('-Inf', 64)).toEqual([-Infinity, null])
42
+ const [nan] = ParseFloat('NaN', 64)
43
+ expect(Number.isNaN(nan)).toBe(true)
44
+ })
45
+ })
@@ -90,17 +90,25 @@ export function bitSizeError(fn: string, str: string, bitSize: number): NumError
90
90
 
91
91
  export let IntSize: number = 64;
92
92
 
93
- // lower(c) is a lower-case letter if and only if
94
- // c is either that lower-case letter or the equivalent upper-case letter.
95
- function lower(c: number): number {
96
- return (c | (120 - 88));
93
+ // digitValue decodes the value of a single base-36 digit, returning 99 (an
94
+ // invalid sentinel above any legal base) for non-alphanumeric runes so the
95
+ // caller rejects them with a syntax error.
96
+ function digitValue(c: string): number {
97
+ if (c >= '0' && c <= '9') {
98
+ return c.charCodeAt(0) - 48;
99
+ }
100
+ const lc = c.toLowerCase();
101
+ if (lc >= 'a' && lc <= 'z') {
102
+ return lc.charCodeAt(0) - 97 + 10;
103
+ }
104
+ return 99;
97
105
  }
98
106
 
99
107
  // ParseUint is like ParseInt but for unsigned numbers.
100
- // A sign prefix is not permitted.
101
- export function ParseUint(s: string, base: number, bitSize: number): [number, $.GoError] {
108
+ // A sign prefix is not permitted. It returns the uint64 value as a bigint.
109
+ export function ParseUint(s: string, base: number, bitSize: number): [bigint, $.GoError] {
102
110
  if (s === "") {
103
- return [0, syntaxError("ParseUint", s)];
111
+ return [0n, syntaxError("ParseUint", s)];
104
112
  }
105
113
 
106
114
  const base0 = base === 0;
@@ -108,7 +116,7 @@ export function ParseUint(s: string, base: number, bitSize: number): [number, $.
108
116
 
109
117
  // Handle base validation
110
118
  if (base < 0 || base === 1 || base > 36) {
111
- return [0, baseError("ParseUint", s0, base)];
119
+ return [0n, baseError("ParseUint", s0, base)];
112
120
  }
113
121
 
114
122
  // Handle base inference
@@ -141,118 +149,128 @@ export function ParseUint(s: string, base: number, bitSize: number): [number, $.
141
149
  if (bitSize === 0) {
142
150
  bitSize = 64;
143
151
  } else if (bitSize < 0 || bitSize > 64) {
144
- return [0, bitSizeError("ParseUint", s0, bitSize)];
152
+ return [0n, bitSizeError("ParseUint", s0, bitSize)];
145
153
  }
146
154
 
147
- // Check for underscores only if base0 (auto-detected base)
155
+ // Underscores are permitted only when the base was auto-detected, and only
156
+ // between digits. Validate against the original string so the base prefix
157
+ // and hex-digit context are visible to underscoreOK.
148
158
  if (base0 && s.includes('_')) {
149
- if (!underscoreOK(s)) {
150
- return [0, syntaxError("ParseUint", s0)];
159
+ if (!underscoreOK(s0)) {
160
+ return [0n, syntaxError("ParseUint", s0)];
151
161
  }
152
162
  s = s.replace(/_/g, '');
153
163
  }
154
164
 
155
- // Use JavaScript's parseInt
156
- const result = parseInt(s, base);
157
-
158
- if (isNaN(result) || !isFinite(result)) {
159
- return [0, syntaxError("ParseUint", s0)];
160
- }
161
-
162
- if (result < 0) {
163
- return [0, syntaxError("ParseUint", s0)];
165
+ if (s === "") {
166
+ return [0n, syntaxError("ParseUint", s0)];
164
167
  }
165
168
 
166
- // Check range for the specified bit size
167
- // Note: JavaScript bitwise operators only work on 32-bit integers,
168
- // so we use Math.pow() for larger bit sizes
169
- let maxVal: number;
170
- if (bitSize >= 53) {
171
- // For 53+ bits, use MAX_SAFE_INTEGER as JavaScript can't represent larger integers accurately
172
- maxVal = Number.MAX_SAFE_INTEGER;
173
- } else {
174
- maxVal = Math.pow(2, bitSize) - 1;
175
- }
176
- if (result > maxVal) {
177
- return [0, rangeError("ParseUint", s0)];
169
+ const baseBig = BigInt(base);
170
+ const maxVal = (1n << BigInt(bitSize)) - 1n;
171
+ let n = 0n;
172
+ for (const ch of s) {
173
+ const d = digitValue(ch);
174
+ if (d >= base) {
175
+ return [0n, syntaxError("ParseUint", s0)];
176
+ }
177
+ n = n * baseBig + BigInt(d);
178
+ if (n > maxVal) {
179
+ return [maxVal, rangeError("ParseUint", s0)];
180
+ }
178
181
  }
179
182
 
180
- return [result, null];
183
+ return [n, null];
181
184
  }
182
185
 
183
186
  // ParseInt interprets a string s in the given base (0, 2 to 36) and
184
- // bit size (0 to 64) and returns the corresponding value i.
185
- export function ParseInt(s: string, base: number, bitSize: number): [number, $.GoError] {
187
+ // bit size (0 to 64) and returns the corresponding value i as a bigint.
188
+ export function ParseInt(s: string, base: number, bitSize: number): [bigint, $.GoError] {
186
189
  if (s === "") {
187
- return [0, syntaxError("ParseInt", s)];
190
+ return [0n, syntaxError("ParseInt", s)];
188
191
  }
189
192
 
193
+ const s0 = s;
190
194
  let neg = false;
191
195
  if (s[0] === '+' || s[0] === '-') {
192
196
  neg = s[0] === '-';
193
197
  s = s.slice(1);
194
198
  if (s.length < 1) {
195
- return [0, syntaxError("ParseInt", s)];
199
+ return [0n, syntaxError("ParseInt", s0)];
196
200
  }
197
201
  }
198
202
 
199
203
  // Convert to unsigned first
200
204
  const [un, err] = ParseUint(s, base, bitSize);
201
- if (err !== null) {
205
+ if (err !== null && (err as NumError).Err !== ErrRange) {
202
206
  const numErr = err as NumError;
203
207
  numErr.Func = "ParseInt";
204
- return [0, err];
208
+ numErr.Num = s0;
209
+ return [0n, numErr];
205
210
  }
206
211
 
207
212
  if (bitSize === 0) {
208
213
  bitSize = 64;
209
214
  }
210
215
 
211
- // Note: JavaScript bitwise operators only work on 32-bit integers,
212
- // so we use Math.pow() for larger bit sizes
213
- let cutoff: number;
214
- if (bitSize >= 53) {
215
- // For 53+ bits, use MAX_SAFE_INTEGER as JavaScript can't represent larger integers accurately
216
- cutoff = Number.MAX_SAFE_INTEGER;
217
- } else {
218
- cutoff = Math.pow(2, bitSize - 1);
219
- }
216
+ const cutoff = 1n << BigInt(bitSize - 1);
220
217
  if (!neg && un >= cutoff) {
221
- return [0, rangeError("ParseInt", s)];
218
+ return [cutoff - 1n, rangeError("ParseInt", s0)];
222
219
  }
223
220
  if (neg && un > cutoff) {
224
- return [0, rangeError("ParseInt", s)];
221
+ return [-cutoff, rangeError("ParseInt", s0)];
225
222
  }
226
223
 
227
- const result = neg ? -un : un;
228
- return [result, null];
224
+ return [neg ? -un : un, null];
229
225
  }
230
226
 
231
227
  // Atoi is equivalent to ParseInt(s, 10, 0), converted to type int.
232
228
  export function Atoi(s: string): [number, $.GoError] {
233
229
  const [i64, err] = ParseInt(s, 10, 0);
234
- return [i64, err];
230
+ return [Number(i64), err];
235
231
  }
236
232
 
237
- // underscoreOK reports whether the underscores in s are allowed.
238
- function underscoreOK(s: string): boolean {
239
- // Simplified validation for underscores
240
- if (s.length === 0) {
241
- return false;
233
+ // underscoreOK reports whether the underscores in s are allowed, matching Go's
234
+ // strconv.underscoreOK: an underscore must sit directly between two digits (or
235
+ // between a base prefix and a digit), never adjacent to a sign, prefix letter,
236
+ // dot, exponent marker, or another underscore. Used by ParseUint and ParseFloat.
237
+ export function underscoreOK(s: string): boolean {
238
+ // saw tracks the previous character class: '^' start, '0' digit or prefix,
239
+ // '_' underscore, '!' anything else.
240
+ let saw = '^';
241
+ let i = 0;
242
+ if (s.length >= 1 && (s[0] === '-' || s[0] === '+')) {
243
+ s = s.slice(1);
242
244
  }
243
-
244
- // Can't start or end with underscore
245
- if (s[0] === '_' || s[s.length - 1] === '_') {
246
- return false;
245
+ let hex = false;
246
+ if (
247
+ s.length >= 2 &&
248
+ s[0] === '0' &&
249
+ (s[1].toLowerCase() === 'b' || s[1].toLowerCase() === 'o' || s[1].toLowerCase() === 'x')
250
+ ) {
251
+ i = 2;
252
+ saw = '0';
253
+ hex = s[1].toLowerCase() === 'x';
247
254
  }
248
-
249
- // Can't have consecutive underscores
250
- for (let i = 0; i < s.length - 1; i++) {
251
- if (s[i] === '_' && s[i + 1] === '_') {
255
+ for (; i < s.length; i++) {
256
+ const c = s[i];
257
+ const lc = c.toLowerCase();
258
+ if ((c >= '0' && c <= '9') || (hex && lc >= 'a' && lc <= 'f')) {
259
+ saw = '0';
260
+ continue;
261
+ }
262
+ if (c === '_') {
263
+ if (saw !== '0') {
264
+ return false;
265
+ }
266
+ saw = '_';
267
+ continue;
268
+ }
269
+ if (saw === '_') {
252
270
  return false;
253
271
  }
272
+ saw = '!';
254
273
  }
255
-
256
- return true;
274
+ return saw !== '_';
257
275
  }
258
276
 
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ErrRange, ErrSyntax, NumError, ParseInt, ParseUint } from './atoi.gs.js'
4
+
5
+ // Ground truth captured from go1.26.4 strconv.ParseUint / ParseInt.
6
+ describe('strconv.ParseUint (Go base + validation)', () => {
7
+ it('rejects trailing non-digit junk', () => {
8
+ const [, err] = ParseUint('12abc', 10, 64)
9
+ expect((err as NumError).Err).toBe(ErrSyntax)
10
+ })
11
+
12
+ it('infers base from 0x, 0b, and 0o prefixes when base is 0', () => {
13
+ expect(ParseUint('0xff', 0, 64)).toEqual([255n, null])
14
+ expect(ParseUint('0b101', 0, 64)).toEqual([5n, null])
15
+ expect(ParseUint('0o17', 0, 64)).toEqual([15n, null])
16
+ expect(ParseUint('0', 0, 64)).toEqual([0n, null])
17
+ })
18
+
19
+ it('enforces the bit-size range ceiling', () => {
20
+ expect(ParseUint('255', 10, 8)).toEqual([255n, null])
21
+ const [v, err] = ParseUint('256', 10, 8)
22
+ expect(v).toBe(255n)
23
+ expect((err as NumError).Err).toBe(ErrRange)
24
+ })
25
+
26
+ it('allows underscores only when the base is auto-detected', () => {
27
+ expect(ParseUint('1_000', 0, 64)).toEqual([1000n, null])
28
+ const [, err] = ParseUint('1_000', 10, 64)
29
+ expect((err as NumError).Err).toBe(ErrSyntax)
30
+ })
31
+ })
32
+
33
+ describe('strconv.ParseInt (Go sign + range)', () => {
34
+ it('parses signed values within range', () => {
35
+ expect(ParseInt('-128', 10, 8)).toEqual([-128n, null])
36
+ expect(ParseInt('+42', 10, 64)).toEqual([42n, null])
37
+ })
38
+
39
+ it('reports ErrRange past the signed ceiling', () => {
40
+ const [v, err] = ParseInt('-129', 10, 8)
41
+ expect(v).toBe(-128n)
42
+ expect((err as NumError).Err).toBe(ErrRange)
43
+ })
44
+
45
+ it('rejects trailing junk', () => {
46
+ const [, err] = ParseInt('12x', 10, 64)
47
+ expect((err as NumError).Err).toBe(ErrSyntax)
48
+ })
49
+ })