teptris 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/teptris.wasm +0 -0
- package/package.json +1 -1
- package/src/index.d.ts +7 -1
- package/src/index.js +85 -26
package/dist/teptris.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teptris",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "TOML for JavaScript/TypeScript at libteptris speed (WebAssembly, no fallback) - version tracks the engine ({engine}.{iteration}; npm semver is 3-part)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/index.d.ts
CHANGED
|
@@ -32,7 +32,13 @@ export interface TeptrisError extends Error {
|
|
|
32
32
|
export interface TeptrisApi {
|
|
33
33
|
/** Parse TOML into a JS object (natural-JSON view: real numbers,
|
|
34
34
|
* booleans, RFC 3339 datetime strings, non-finite floats as
|
|
35
|
-
* null). Throws TeptrisError on invalid input.
|
|
35
|
+
* null). Throws TeptrisError on invalid input.
|
|
36
|
+
*
|
|
37
|
+
* Precision floor: JS numbers lose precision past 2^53, so
|
|
38
|
+
* integer literals beyond that round (9223372036854775807 reads
|
|
39
|
+
* back as 9223372036854776000). dump() never wraps: values past
|
|
40
|
+
* the int64 bounds emit as floats, and bigints outside int64
|
|
41
|
+
* throw. */
|
|
36
42
|
loads(toml: string): TomlTable;
|
|
37
43
|
|
|
38
44
|
/** Serialize a table to canonical TOML via the engine builder.
|
package/src/index.js
CHANGED
|
@@ -28,7 +28,9 @@ function parseDateTime(str) {
|
|
|
28
28
|
return {
|
|
29
29
|
kind, year: +m[1], month: +m[2], day: +m[3], hour: +m[4],
|
|
30
30
|
minute: +m[5], second: +m[6],
|
|
31
|
-
|
|
31
|
+
// integer string padding, not FP: 0.507029 * 1e9 floors to
|
|
32
|
+
// 507028999 (off by one)
|
|
33
|
+
nanosecond: m[7] ? +((m[7] + "000000000").slice(0, 9)) : 0,
|
|
32
34
|
offset_seconds: offset,
|
|
33
35
|
};
|
|
34
36
|
}
|
|
@@ -42,7 +44,7 @@ function parseDateTime(str) {
|
|
|
42
44
|
if (m) {
|
|
43
45
|
return { kind: KIND_TIME, year: 0, month: 0, day: 0, hour: +m[1],
|
|
44
46
|
minute: +m[2], second: +m[3],
|
|
45
|
-
nanosecond: m[4] ?
|
|
47
|
+
nanosecond: m[4] ? +((m[4] + "000000000").slice(0, 9)) : 0,
|
|
46
48
|
offset_seconds: 0 };
|
|
47
49
|
}
|
|
48
50
|
return null;
|
|
@@ -51,29 +53,44 @@ function parseDateTime(str) {
|
|
|
51
53
|
const RFC3339 =
|
|
52
54
|
/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$|^\d{2}:\d{2}:\d{2}(\.\d+)?$/;
|
|
53
55
|
|
|
56
|
+
// int64 bounds for the dump integer path: Number.isInteger is true for
|
|
57
|
+
// values like 1e21, and the op-stream's int64 encode wraps silently
|
|
58
|
+
// past these bounds. The bound is 2^63 EXCLUSIVE (exactly representable
|
|
59
|
+
// as a number; 2^63-1 is not - its literal rounds up to 2^63).
|
|
60
|
+
// BigInt(number) of an integral in-range f64 is exact, so anything
|
|
61
|
+
// between 2^53 and 2^63 is still lossless.
|
|
62
|
+
const I64_BOUND = 9223372036854775808; /* 2 ** 63 */
|
|
63
|
+
const I64_MIN_BIG = -9223372036854775808n;
|
|
64
|
+
const I64_MAX_BIG = 9223372036854775807n;
|
|
65
|
+
|
|
54
66
|
// cheap charset pre-check before the RFC 3339 regex: datetimes start
|
|
55
67
|
// with a digit and only ever contain digits, date/time punctuation
|
|
56
|
-
// and TZ marks - rejects nearly all plain strings without the regex
|
|
57
|
-
|
|
68
|
+
// and TZ marks - rejects nearly all plain strings without the regex.
|
|
69
|
+
// One whole-string regex pass (the per-char loop allocated a
|
|
70
|
+
// single-char string per character).
|
|
71
|
+
const DATETIME_CHARS = /^[0-9Tt: .+Zz-]*$/;
|
|
58
72
|
const utf8 = new TextDecoder("utf-8");
|
|
73
|
+
const utf8enc = new TextEncoder();
|
|
59
74
|
function maybeRfc3339(v) {
|
|
60
75
|
const c0 = v.charCodeAt(0);
|
|
61
|
-
|
|
62
|
-
for (let i = 1; i < v.length; i++) {
|
|
63
|
-
if (!DATETIME_CHARS.test(v[i])) return false;
|
|
64
|
-
}
|
|
65
|
-
return true;
|
|
76
|
+
return c0 >= 48 && c0 <= 57 && DATETIME_CHARS.test(v);
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
function buildValue(mod, b, key, v, errs) {
|
|
69
80
|
const kp = key == null ? 0 : cstr(mod, key);
|
|
70
81
|
try {
|
|
71
82
|
if (v === null || v === undefined) {
|
|
72
|
-
throw new Error("null/undefined cannot round-trip through TOML");
|
|
83
|
+
throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
|
|
73
84
|
}
|
|
74
85
|
switch (typeof v) {
|
|
75
86
|
case "number":
|
|
76
|
-
|
|
87
|
+
// same int64-exactness guard as the op-stream path: put_integer
|
|
88
|
+
// truncates BigInts past the int64 bounds, and -0.0 keeps its
|
|
89
|
+
// sign only as a float
|
|
90
|
+
if (Object.is(v, -0)) {
|
|
91
|
+
mod._teptris_wasm_builder_put_float(b, kp, v);
|
|
92
|
+
} else if (Number.isInteger(v) &&
|
|
93
|
+
v >= -I64_BOUND && v < I64_BOUND) {
|
|
77
94
|
mod._teptris_wasm_builder_put_integer(b, kp, BigInt(v));
|
|
78
95
|
} else {
|
|
79
96
|
mod._teptris_wasm_builder_put_float(b, kp, v);
|
|
@@ -177,7 +194,9 @@ export function initApi(mod) {
|
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
function readOut(ptr) {
|
|
180
|
-
|
|
197
|
+
// explicit length: the emit buffer is not NUL-terminated for us,
|
|
198
|
+
// and a full strlen scan over the output is measurable
|
|
199
|
+
const out = UTF8ToString(ptr, mod._teptris_wasm_toml_len());
|
|
181
200
|
mod._teptris_wasm_free(ptr);
|
|
182
201
|
return out;
|
|
183
202
|
}
|
|
@@ -189,7 +208,7 @@ export function initApi(mod) {
|
|
|
189
208
|
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
190
209
|
throw new TypeError("dump expects a table object (TOML roots are tables)");
|
|
191
210
|
}
|
|
192
|
-
|
|
211
|
+
|
|
193
212
|
let buf = new Uint8Array(1 << 16);
|
|
194
213
|
let n = 0;
|
|
195
214
|
let dv = new DataView(buf.buffer);
|
|
@@ -226,24 +245,53 @@ export function initApi(mod) {
|
|
|
226
245
|
const wkey = (k) => {
|
|
227
246
|
ensure(2 + k.length * 3);
|
|
228
247
|
buf[n++] = 1;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
248
|
+
// ASCII fast path: keys are tiny, so TextEncoder.encodeInto's
|
|
249
|
+
// per-call view allocation costs more than the copy itself;
|
|
250
|
+
// non-ASCII falls back to encodeInto (overwrites from offset 1)
|
|
251
|
+
let ascii = true;
|
|
252
|
+
for (let i = 0; i < k.length; i++) {
|
|
253
|
+
const c = k.charCodeAt(i);
|
|
254
|
+
if (c > 127) { ascii = false; break; }
|
|
255
|
+
buf[n + 1 + i] = c;
|
|
256
|
+
}
|
|
257
|
+
if (ascii) {
|
|
258
|
+
buf[n] = k.length;
|
|
259
|
+
n += 1 + k.length;
|
|
260
|
+
} else {
|
|
261
|
+
const { written } = utf8enc.encodeInto(k, buf.subarray(n + 1));
|
|
262
|
+
buf[n] = written;
|
|
263
|
+
n += 1 + written;
|
|
264
|
+
}
|
|
232
265
|
};
|
|
233
266
|
const scalar = (k, v) => {
|
|
234
267
|
if (k === null) { ensure(2); buf[n++] = 1; buf[n++] = 0; }
|
|
235
268
|
else wkey(k);
|
|
236
269
|
if (typeof v === "boolean") { byte(v ? 6 : 5); return; }
|
|
237
270
|
if (typeof v === "number") {
|
|
238
|
-
|
|
239
|
-
|
|
271
|
+
// the integer path only for numbers the int64 op can carry
|
|
272
|
+
// EXACTLY: Number.isInteger alone is true for 1e21 and for
|
|
273
|
+
// loads-rounded values past i64max, and setBigInt64 silently
|
|
274
|
+
// WRAPS out-of-range values (9223372036854775807 dumped as
|
|
275
|
+
// INT64_MIN); -0.0 must keep its sign, so it goes to the
|
|
276
|
+
// float path too
|
|
277
|
+
if (Object.is(v, -0)) { byte(4); f64(v); return; }
|
|
278
|
+
if (Number.isInteger(v) &&
|
|
279
|
+
v >= -I64_BOUND && v < I64_BOUND) { byte(3); i64(v); return; }
|
|
280
|
+
byte(4); f64(v);
|
|
240
281
|
return;
|
|
241
282
|
}
|
|
242
|
-
if (typeof v === "bigint") {
|
|
283
|
+
if (typeof v === "bigint") {
|
|
284
|
+
if (v < I64_MIN_BIG || v > I64_MAX_BIG) {
|
|
285
|
+
throw new Error(`bigint out of int64 range: ${v}`);
|
|
286
|
+
}
|
|
287
|
+
byte(3); i64(v); return;
|
|
288
|
+
}
|
|
243
289
|
if (typeof v === "string") {
|
|
244
290
|
if (maybeRfc3339(v) && RFC3339.test(v)) {
|
|
245
291
|
const dt = parseDateTime(v);
|
|
246
|
-
|
|
292
|
+
// the key prefix is already written (top of scalar()); the
|
|
293
|
+
// datetime record is value-only - re-sending the key here
|
|
294
|
+
// crashed bare array elements (k === null)
|
|
247
295
|
ensure(27);
|
|
248
296
|
buf[n++] = 7;
|
|
249
297
|
buf[n++] = dt.kind;
|
|
@@ -256,12 +304,23 @@ export function initApi(mod) {
|
|
|
256
304
|
}
|
|
257
305
|
ensure(5 + v.length * 3);
|
|
258
306
|
buf[n++] = 2;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
307
|
+
let ascii = true;
|
|
308
|
+
for (let i = 0; i < v.length; i++) {
|
|
309
|
+
const c = v.charCodeAt(i);
|
|
310
|
+
if (c > 127) { ascii = false; break; }
|
|
311
|
+
buf[n + 4 + i] = c;
|
|
312
|
+
}
|
|
313
|
+
if (ascii) {
|
|
314
|
+
dv.setUint32(n, v.length, true);
|
|
315
|
+
n += 4 + v.length;
|
|
316
|
+
} else {
|
|
317
|
+
const { written } = utf8enc.encodeInto(v, buf.subarray(n + 4));
|
|
318
|
+
dv.setUint32(n, written, true);
|
|
319
|
+
n += 4 + written;
|
|
320
|
+
}
|
|
262
321
|
return;
|
|
263
322
|
}
|
|
264
|
-
throw new Error("null/undefined cannot round-trip through TOML");
|
|
323
|
+
throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
|
|
265
324
|
};
|
|
266
325
|
const isScalar = (v) =>
|
|
267
326
|
v !== null && (typeof v !== "object" || Array.isArray(v) === false &&
|
|
@@ -277,7 +336,7 @@ export function initApi(mod) {
|
|
|
277
336
|
const k = keys[i], v = o[k];
|
|
278
337
|
if (isScalar(v)) continue;
|
|
279
338
|
if (v === null || v === undefined) {
|
|
280
|
-
throw new Error("null/undefined cannot round-trip through TOML");
|
|
339
|
+
throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
|
|
281
340
|
}
|
|
282
341
|
wkey(k);
|
|
283
342
|
if (Array.isArray(v)) {
|
|
@@ -309,7 +368,7 @@ export function initApi(mod) {
|
|
|
309
368
|
return;
|
|
310
369
|
}
|
|
311
370
|
if (el === null || el === undefined) {
|
|
312
|
-
throw new Error("null/undefined cannot round-trip through TOML");
|
|
371
|
+
throw new Error("null/undefined cannot round-trip through TOML (nan/inf parse to null in loads() and cannot re-enter)");
|
|
313
372
|
}
|
|
314
373
|
byte(0x0B);
|
|
315
374
|
members(el, true);
|