json-as 0.5.52 → 0.5.54
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/.github/workflows/nodejs.yml +25 -0
- package/README.md +0 -23
- package/assembly/__benches__/as-json.ts +8 -62
- package/assembly/__tests__/as-json.spec.ts +4 -13
- package/assembly/src/json.ts +40 -71
- package/assembly/src/util.ts +159 -143
- package/assembly/test.ts +4 -87
- package/bench-results/INTEGER-PARSING.md +52 -0
- package/package.json +6 -6
- package/transform/package.json +1 -1
- package/asconfig.json +0 -15
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Node.js CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout the repository
|
|
12
|
+
uses: actions/checkout@v2
|
|
13
|
+
|
|
14
|
+
- name: Setup Node.js
|
|
15
|
+
uses: actions/setup-node@v2
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
if: steps.node-cache.outputs.cache-hit != 'true'
|
|
19
|
+
run: yarn
|
|
20
|
+
|
|
21
|
+
- name: Test to see if the project compiles
|
|
22
|
+
run: yarn build:test
|
|
23
|
+
|
|
24
|
+
- name: Perform tests
|
|
25
|
+
run: yarn run test:aspect
|
package/README.md
CHANGED
|
@@ -68,29 +68,6 @@ const stringified = JSON.stringify<Player>(player);
|
|
|
68
68
|
const parsed = JSON.parse<Player>(stringified);
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
## Deviations from the spec
|
|
72
|
-
|
|
73
|
-
This implementation does not hold strongly to the JSON specification. Rather, design and behavior are inspired by the JSON implementation found in Google's v8 engine.
|
|
74
|
-
|
|
75
|
-
- No support for dynamic types
|
|
76
|
-
- Unsafe by design--parser assumes valid JSON
|
|
77
|
-
- Partial whitespace support--parser prefers speed over handling whitespace effectively. Users may use the `removeWhitespace` function provided by `json-as/src/util.ts`
|
|
78
|
-
- Is not based off of the official spec, but rather the behavior of the JSON C implementation found in google's v8 engine
|
|
79
|
-
- Support for scientific notation on integers. Float support coming soon.
|
|
80
|
-
|
|
81
|
-
## Implemented features
|
|
82
|
-
|
|
83
|
-
Fully supports:
|
|
84
|
-
|
|
85
|
-
- Strings
|
|
86
|
-
- Integers
|
|
87
|
-
- Floats (Scientific notation not implemented)
|
|
88
|
-
- Booleans
|
|
89
|
-
- Arrays
|
|
90
|
-
- Objects
|
|
91
|
-
- Date
|
|
92
|
-
- Null
|
|
93
|
-
|
|
94
71
|
## Performance
|
|
95
72
|
|
|
96
73
|
Here are some benchmarks I took with `tinybench` (JavaScript) and `astral` (AssemblyScript).
|
|
@@ -1,64 +1,10 @@
|
|
|
1
1
|
import { JSON } from "..";
|
|
2
|
-
import {
|
|
3
|
-
import { atoi_fast, parseSciInteger, snip_fast, unsafeCharCodeAt } from "../src/util";
|
|
4
|
-
import { HASH } from "util/hash";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let last = 1;
|
|
8
|
-
let char = 0;
|
|
9
|
-
let inStr = false;
|
|
10
|
-
let key: string | null = null;
|
|
11
|
-
let pos = 0;
|
|
12
|
-
|
|
2
|
+
import { snip_fast } from "../src/util";
|
|
13
3
|
@json
|
|
14
4
|
class Vec3 {
|
|
15
5
|
x: i32;
|
|
16
6
|
y: i32;
|
|
17
7
|
z: i32;
|
|
18
|
-
@inline __JSON_Deserialize(
|
|
19
|
-
data: string,
|
|
20
|
-
to: Vec3
|
|
21
|
-
): Vec3 {
|
|
22
|
-
for (; pos < data.length - 1; pos++) {
|
|
23
|
-
char = unsafeCharCodeAt(data, pos);
|
|
24
|
-
if (inStr === false && char === quoteCode) {
|
|
25
|
-
if (key != null) {
|
|
26
|
-
if (unsafeCharCodeAt(key!, 0) == 120) {
|
|
27
|
-
to.x = parseSciInteger<i32>(data.substring(last, pos - 1));
|
|
28
|
-
} else if (unsafeCharCodeAt(key!, 0) == 121) {
|
|
29
|
-
to.y = parseSciInteger<i32>(data.substring(last, pos - 1));
|
|
30
|
-
} else if (unsafeCharCodeAt(key!, 0) == 122) {
|
|
31
|
-
to.z = parseSciInteger<i32>(data.substring(last, pos - 1));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
last = ++pos;
|
|
35
|
-
inStr = true;
|
|
36
|
-
} else if (
|
|
37
|
-
char === quoteCode &&
|
|
38
|
-
unsafeCharCodeAt(data, pos - 1) != backSlashCode
|
|
39
|
-
) {
|
|
40
|
-
inStr = false;
|
|
41
|
-
key = data.substring(last, pos);
|
|
42
|
-
last = pos += 2;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (key != null) {
|
|
46
|
-
if (unsafeCharCodeAt(key!, 0) == 120) {
|
|
47
|
-
to.x = parseSciInteger<i32>(data.substring(last, pos - 1));
|
|
48
|
-
} else if (unsafeCharCodeAt(key!, 0) == 121) {
|
|
49
|
-
to.y = parseSciInteger<i32>(data.substring(last, pos - 1));
|
|
50
|
-
} else if (unsafeCharCodeAt(key!, 0) == 122) {
|
|
51
|
-
to.z = parseSciInteger<i32>(data.substring(last, pos - 1));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
last = 1;
|
|
56
|
-
char = 0;
|
|
57
|
-
inStr = false;
|
|
58
|
-
key = null;
|
|
59
|
-
pos = 0;
|
|
60
|
-
return to;
|
|
61
|
-
}
|
|
62
8
|
}
|
|
63
9
|
|
|
64
10
|
const vec: Vec3 = {
|
|
@@ -70,7 +16,7 @@ const vec: Vec3 = {
|
|
|
70
16
|
bench("Parse Number SNIP", () => {
|
|
71
17
|
blackbox<i32>(snip_fast<i32>("12345"));
|
|
72
18
|
});
|
|
73
|
-
|
|
19
|
+
/*
|
|
74
20
|
bench("Parse Number ATOI", () => {
|
|
75
21
|
blackbox<i32>(atoi_fast<i32>("12345"));
|
|
76
22
|
})
|
|
@@ -82,15 +28,15 @@ bench("Parse Number STDLIB", () => {
|
|
|
82
28
|
bench("Parse Number OLD", () => {
|
|
83
29
|
blackbox<i32>(parseSciInteger<i32>("12345"));
|
|
84
30
|
});
|
|
85
|
-
|
|
31
|
+
*/
|
|
86
32
|
bench("Stringify Object (Vec3)", () => {
|
|
87
33
|
blackbox<string>(vec.__JSON_Serialize());
|
|
88
34
|
});
|
|
89
35
|
|
|
90
36
|
// TODO: Make this allocate without crashing
|
|
91
|
-
|
|
92
|
-
blackbox<Vec3>(vec.__JSON_Deserialize('{"x":0,"y":0,"z":0}', vec));
|
|
93
|
-
})
|
|
37
|
+
//bench("Parse Object (Vec3)", () => {
|
|
38
|
+
// blackbox<Vec3>(vec.__JSON_Deserialize('{"x":0,"y":0,"z":0}', vec));
|
|
39
|
+
//});
|
|
94
40
|
|
|
95
41
|
bench("Stringify Number Array", () => {
|
|
96
42
|
blackbox(JSON.stringify<i32[]>([1, 2, 3]));
|
|
@@ -126,12 +72,12 @@ bench("Parse Boolean", () => {
|
|
|
126
72
|
|
|
127
73
|
bench("Stringify Integer", () => {
|
|
128
74
|
blackbox(JSON.stringify(blackbox(314)));
|
|
129
|
-
})
|
|
75
|
+
});*/
|
|
130
76
|
|
|
131
77
|
bench("Parse Integer", () => {
|
|
132
78
|
blackbox(JSON.parse<i32>(blackbox("314")));
|
|
133
79
|
});
|
|
134
|
-
|
|
80
|
+
/*
|
|
135
81
|
bench("Stringify Float", () => {
|
|
136
82
|
blackbox(JSON.stringify(blackbox(3.14)));
|
|
137
83
|
});
|
|
@@ -46,10 +46,10 @@ describe("Ser/de Numbers", () => {
|
|
|
46
46
|
it("should ser/de integers", () => {
|
|
47
47
|
canSerde<i32>(0);
|
|
48
48
|
|
|
49
|
-
canSerde<u32>(100);
|
|
50
|
-
canSerde<u64>(101);
|
|
51
|
-
canSerde<i32>(-100);
|
|
52
|
-
canSerde<i64>(-101);
|
|
49
|
+
canSerde<u32>(100, "100");
|
|
50
|
+
canSerde<u64>(101, "101");
|
|
51
|
+
canSerde<i32>(-100, "-100");
|
|
52
|
+
canSerde<i64>(-101, "-101");
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it("should ser/de floats", () => {
|
|
@@ -68,15 +68,6 @@ describe("Ser/de Numbers", () => {
|
|
|
68
68
|
canSerde<boolean>(true);
|
|
69
69
|
canSerde<boolean>(false);
|
|
70
70
|
});
|
|
71
|
-
|
|
72
|
-
it("should ser/de BigInt objects", () => {
|
|
73
|
-
canSerde<i32>(0);
|
|
74
|
-
|
|
75
|
-
canSerde<u32>(100);
|
|
76
|
-
canSerde<u64>(101);
|
|
77
|
-
canSerde<i32>(-100);
|
|
78
|
-
canSerde<i64>(-101);
|
|
79
|
-
});
|
|
80
71
|
});
|
|
81
72
|
|
|
82
73
|
describe("Ser/de Array", () => {
|
package/assembly/src/json.ts
CHANGED
|
@@ -35,14 +35,11 @@ export namespace JSON {
|
|
|
35
35
|
* @param data T
|
|
36
36
|
* @returns string
|
|
37
37
|
*/
|
|
38
|
-
// @ts-ignore
|
|
39
|
-
@inline export function stringify<
|
|
40
|
-
T
|
|
41
|
-
>(data: T): string {
|
|
38
|
+
// @ts-ignore: Decorator
|
|
39
|
+
@inline export function stringify<T>(data: T): string {
|
|
42
40
|
// String
|
|
43
41
|
if (isString<T>() && data != null) {
|
|
44
|
-
|
|
45
|
-
return serializeString(data);
|
|
42
|
+
return serializeString(data as string);
|
|
46
43
|
} else if (isBoolean<T>()) {
|
|
47
44
|
return data ? "true" : "false";
|
|
48
45
|
} else if (isNullable<T>() && data == null) {
|
|
@@ -51,9 +48,9 @@ export namespace JSON {
|
|
|
51
48
|
} else if ((isInteger<T>() || isFloat<T>()) && isFinite(data)) {
|
|
52
49
|
// @ts-ignore
|
|
53
50
|
return data.toString();
|
|
54
|
-
// @ts-ignore
|
|
51
|
+
// @ts-ignore: Hidden function
|
|
55
52
|
} else if (isDefined(data.__JSON_Serialize)) {
|
|
56
|
-
// @ts-ignore
|
|
53
|
+
// @ts-ignore: Hidden function
|
|
57
54
|
return data.__JSON_Serialize();
|
|
58
55
|
} else if (data instanceof Date) {
|
|
59
56
|
return data.toISOString();
|
|
@@ -110,10 +107,8 @@ export namespace JSON {
|
|
|
110
107
|
* @returns T
|
|
111
108
|
*/
|
|
112
109
|
|
|
113
|
-
// @ts-ignore
|
|
114
|
-
@inline export function parse<
|
|
115
|
-
T
|
|
116
|
-
>(data: string): T {
|
|
110
|
+
// @ts-ignore: Decorator
|
|
111
|
+
@inline export function parse<T>(data: string): T {
|
|
117
112
|
let type: T;
|
|
118
113
|
if (isString<T>()) {
|
|
119
114
|
// @ts-ignore
|
|
@@ -143,20 +138,17 @@ export namespace JSON {
|
|
|
143
138
|
);
|
|
144
139
|
}
|
|
145
140
|
}
|
|
146
|
-
// @ts-ignore
|
|
147
|
-
@inline function parseObjectValue<
|
|
148
|
-
T
|
|
149
|
-
>(data: string): T {
|
|
141
|
+
// @ts-ignore: Decorator
|
|
142
|
+
@inline function parseObjectValue<T>(data: string): T {
|
|
150
143
|
let type: T;
|
|
151
144
|
if (isString<T>()) {
|
|
152
145
|
// @ts-ignore
|
|
153
146
|
let result = "";
|
|
154
147
|
let last = 0;
|
|
155
|
-
let char = 0;
|
|
156
148
|
for (let i = 0; i < data.length; i++) {
|
|
157
149
|
// \\"
|
|
158
150
|
if (unsafeCharCodeAt(data, i) === backSlashCode) {
|
|
159
|
-
char = unsafeCharCodeAt(data, ++i);
|
|
151
|
+
const char = unsafeCharCodeAt(data, ++i);
|
|
160
152
|
result += data.slice(last, i - 1);
|
|
161
153
|
if (char === 34) {
|
|
162
154
|
result += '"';
|
|
@@ -223,10 +215,8 @@ export namespace JSON {
|
|
|
223
215
|
}
|
|
224
216
|
}
|
|
225
217
|
|
|
226
|
-
// @ts-ignore
|
|
227
|
-
@inline function serializeString(
|
|
228
|
-
data: string
|
|
229
|
-
): string {
|
|
218
|
+
// @ts-ignore: Decorator
|
|
219
|
+
@inline function serializeString(data: string): string {
|
|
230
220
|
// @ts-ignore
|
|
231
221
|
//if (data.length === 0) return "\"\"";
|
|
232
222
|
/*
|
|
@@ -309,10 +299,8 @@ export namespace JSON {
|
|
|
309
299
|
return result + '"';
|
|
310
300
|
}
|
|
311
301
|
|
|
312
|
-
// @ts-ignore
|
|
313
|
-
@inline function parseString(
|
|
314
|
-
data: string
|
|
315
|
-
): string {
|
|
302
|
+
// @ts-ignore: Decorator
|
|
303
|
+
@inline function parseString(data: string): string {
|
|
316
304
|
let result = "";
|
|
317
305
|
let last = 1;
|
|
318
306
|
for (let i = 1; i < data.length - 1; i++) {
|
|
@@ -375,16 +363,14 @@ export namespace JSON {
|
|
|
375
363
|
return result;
|
|
376
364
|
}
|
|
377
365
|
|
|
378
|
-
// @ts-ignore
|
|
379
|
-
@inline function parseBoolean<
|
|
380
|
-
T extends boolean
|
|
381
|
-
>(data: string): T {
|
|
366
|
+
// @ts-ignore: Decorator
|
|
367
|
+
@inline function parseBoolean<T extends boolean>(data: string): T {
|
|
382
368
|
if (data.length > 3 && data.startsWith("true")) return <T>true;
|
|
383
369
|
else if (data.length > 4 && data.startsWith("false")) return <T>false;
|
|
384
370
|
else throw new Error(`JSON: Cannot parse "${data}" as boolean`);
|
|
385
371
|
}
|
|
386
372
|
|
|
387
|
-
// @ts-ignore
|
|
373
|
+
// @ts-ignore: Decorator
|
|
388
374
|
@inline export function parseNumber<T>(data: string): T {
|
|
389
375
|
if (isInteger<T>()) {
|
|
390
376
|
// @ts-ignore
|
|
@@ -398,7 +384,7 @@ export namespace JSON {
|
|
|
398
384
|
else if (type instanceof f32) return f32.parse(data);
|
|
399
385
|
}
|
|
400
386
|
|
|
401
|
-
// @ts-ignore
|
|
387
|
+
// @ts-ignore: Decorator
|
|
402
388
|
@inline function parseObject<T>(data: string): T {
|
|
403
389
|
let schema: nonnull<T> = changetype<nonnull<T>>(
|
|
404
390
|
__new(offsetof<nonnull<T>>(), idof<nonnull<T>>())
|
|
@@ -406,17 +392,16 @@ export namespace JSON {
|
|
|
406
392
|
let key = "";
|
|
407
393
|
let isKey = false;
|
|
408
394
|
let depth = 0;
|
|
409
|
-
let char = 0;
|
|
410
395
|
let outerLoopIndex = 1;
|
|
411
396
|
for (; outerLoopIndex < data.length - 1; outerLoopIndex++) {
|
|
412
|
-
char = unsafeCharCodeAt(data, outerLoopIndex);
|
|
397
|
+
const char = unsafeCharCodeAt(data, outerLoopIndex);
|
|
413
398
|
if (char === leftBracketCode) {
|
|
414
399
|
for (
|
|
415
400
|
let arrayValueIndex = outerLoopIndex;
|
|
416
401
|
arrayValueIndex < data.length - 1;
|
|
417
402
|
arrayValueIndex++
|
|
418
403
|
) {
|
|
419
|
-
char = unsafeCharCodeAt(data, arrayValueIndex);
|
|
404
|
+
const char = unsafeCharCodeAt(data, arrayValueIndex);
|
|
420
405
|
if (char === leftBracketCode) {
|
|
421
406
|
depth++;
|
|
422
407
|
} else if (char === rightBracketCode) {
|
|
@@ -440,7 +425,7 @@ export namespace JSON {
|
|
|
440
425
|
objectValueIndex < data.length - 1;
|
|
441
426
|
objectValueIndex++
|
|
442
427
|
) {
|
|
443
|
-
char = unsafeCharCodeAt(data, objectValueIndex);
|
|
428
|
+
const char = unsafeCharCodeAt(data, objectValueIndex);
|
|
444
429
|
if (char === leftBraceCode) {
|
|
445
430
|
depth++;
|
|
446
431
|
} else if (char === rightBraceCode) {
|
|
@@ -464,7 +449,7 @@ export namespace JSON {
|
|
|
464
449
|
stringValueIndex < data.length - 1;
|
|
465
450
|
stringValueIndex++
|
|
466
451
|
) {
|
|
467
|
-
char = unsafeCharCodeAt(data, stringValueIndex);
|
|
452
|
+
const char = unsafeCharCodeAt(data, stringValueIndex);
|
|
468
453
|
if (
|
|
469
454
|
char === quoteCode &&
|
|
470
455
|
unsafeCharCodeAt(data, stringValueIndex - 1) !== backSlashCode
|
|
@@ -510,7 +495,7 @@ export namespace JSON {
|
|
|
510
495
|
} else if ((char >= 48 && char <= 57) || char === 45) {
|
|
511
496
|
let numberValueIndex = ++outerLoopIndex;
|
|
512
497
|
for (; numberValueIndex < data.length; numberValueIndex++) {
|
|
513
|
-
char = unsafeCharCodeAt(data, numberValueIndex);
|
|
498
|
+
const char = unsafeCharCodeAt(data, numberValueIndex);
|
|
514
499
|
if (char === commaCode || char === rightBraceCode || isSpace(char)) {
|
|
515
500
|
// @ts-ignore
|
|
516
501
|
schema.__JSON_Set_Key(
|
|
@@ -527,10 +512,8 @@ export namespace JSON {
|
|
|
527
512
|
return schema;
|
|
528
513
|
}
|
|
529
514
|
|
|
530
|
-
// @ts-ignore
|
|
531
|
-
@inline function parseArray<
|
|
532
|
-
T extends unknown[]
|
|
533
|
-
>(data: string): T {
|
|
515
|
+
// @ts-ignore: Decorator
|
|
516
|
+
@inline function parseArray<T extends unknown[]>(data: string): T {
|
|
534
517
|
if (isString<valueof<T>>()) {
|
|
535
518
|
return <T>parseStringArray(data);
|
|
536
519
|
} else if (isBoolean<valueof<T>>()) {
|
|
@@ -558,10 +541,8 @@ export namespace JSON {
|
|
|
558
541
|
return unreachable();
|
|
559
542
|
}
|
|
560
543
|
|
|
561
|
-
// @ts-ignore
|
|
562
|
-
@inline function parseStringArray(
|
|
563
|
-
data: string
|
|
564
|
-
): string[] {
|
|
544
|
+
// @ts-ignore: Decorator
|
|
545
|
+
@inline function parseStringArray(data: string): string[] {
|
|
565
546
|
const result: string[] = [];
|
|
566
547
|
let lastPos = 0;
|
|
567
548
|
let instr = false;
|
|
@@ -579,15 +560,12 @@ export namespace JSON {
|
|
|
579
560
|
return result;
|
|
580
561
|
}
|
|
581
562
|
|
|
582
|
-
// @ts-ignore
|
|
583
|
-
@inline function parseBooleanArray<
|
|
584
|
-
T extends boolean[]
|
|
585
|
-
>(data: string): T {
|
|
563
|
+
// @ts-ignore: Decorator
|
|
564
|
+
@inline function parseBooleanArray<T extends boolean[]>(data: string): T {
|
|
586
565
|
const result = instantiate<T>();
|
|
587
566
|
let lastPos = 1;
|
|
588
|
-
let char = 0;
|
|
589
567
|
for (let i = 1; i < data.length - 1; i++) {
|
|
590
|
-
char = unsafeCharCodeAt(data, i);
|
|
568
|
+
const char = unsafeCharCodeAt(data, i);
|
|
591
569
|
/*// if char == "t" && i+3 == "e"
|
|
592
570
|
if (char === tCode && data.charCodeAt(i + 3) === eCode) {
|
|
593
571
|
//i += 3;
|
|
@@ -608,16 +586,13 @@ export namespace JSON {
|
|
|
608
586
|
return result;
|
|
609
587
|
}
|
|
610
588
|
|
|
611
|
-
// @ts-ignore
|
|
612
|
-
@inline function parseNumberArray<
|
|
613
|
-
T extends number[]
|
|
614
|
-
>(data: string): T {
|
|
589
|
+
// @ts-ignore: Decorator
|
|
590
|
+
@inline function parseNumberArray<T extends number[]>(data: string): T {
|
|
615
591
|
const result = instantiate<T>();
|
|
616
592
|
let lastPos = 0;
|
|
617
|
-
let char = 0;
|
|
618
593
|
let i = 1;
|
|
619
594
|
for (; i < data.length - 1; i++) {
|
|
620
|
-
char = unsafeCharCodeAt(data, i);
|
|
595
|
+
const char = unsafeCharCodeAt(data, i);
|
|
621
596
|
if ((lastPos === 0 && char >= 48 && char <= 57) || char === 45) {
|
|
622
597
|
lastPos = i;
|
|
623
598
|
} else if ((isSpace(char) || char == commaCode) && lastPos > 0) {
|
|
@@ -626,7 +601,7 @@ export namespace JSON {
|
|
|
626
601
|
}
|
|
627
602
|
}
|
|
628
603
|
for (; i > lastPos - 1; i--) {
|
|
629
|
-
char = unsafeCharCodeAt(data, i);
|
|
604
|
+
const char = unsafeCharCodeAt(data, i);
|
|
630
605
|
if (char !== rightBracketCode) {
|
|
631
606
|
result.push(parseNumber<valueof<T>>(data.slice(lastPos, i + 1)));
|
|
632
607
|
break;
|
|
@@ -635,12 +610,9 @@ export namespace JSON {
|
|
|
635
610
|
return result;
|
|
636
611
|
}
|
|
637
612
|
|
|
638
|
-
// @ts-ignore
|
|
639
|
-
@inline function parseArrayArray<
|
|
640
|
-
T extends unknown[][]
|
|
641
|
-
>(data: string): T {
|
|
613
|
+
// @ts-ignore: Decorator
|
|
614
|
+
@inline function parseArrayArray<T extends unknown[][]>(data: string): T {
|
|
642
615
|
const result = instantiate<T>();
|
|
643
|
-
let char = 0;
|
|
644
616
|
let lastPos = 0;
|
|
645
617
|
let depth = 0;
|
|
646
618
|
let i = 1;
|
|
@@ -648,7 +620,7 @@ export namespace JSON {
|
|
|
648
620
|
//for (; unsafeCharCodeAt(data, i) !== leftBracketCode; i++) {}
|
|
649
621
|
//i++;
|
|
650
622
|
for (; i < data.length - 1; i++) {
|
|
651
|
-
char = unsafeCharCodeAt(data, i);
|
|
623
|
+
const char = unsafeCharCodeAt(data, i);
|
|
652
624
|
if (char === leftBracketCode) {
|
|
653
625
|
if (depth === 0) {
|
|
654
626
|
lastPos = i;
|
|
@@ -666,16 +638,13 @@ export namespace JSON {
|
|
|
666
638
|
return result;
|
|
667
639
|
}
|
|
668
640
|
|
|
669
|
-
// @ts-ignore
|
|
670
|
-
@inline export function parseObjectArray<
|
|
671
|
-
T extends unknown[]
|
|
672
|
-
>(data: string): T {
|
|
641
|
+
// @ts-ignore: Decorator
|
|
642
|
+
@inline export function parseObjectArray<T extends unknown[]>(data: string): T {
|
|
673
643
|
const result = instantiate<T>();
|
|
674
|
-
let char = 0;
|
|
675
644
|
let lastPos: u32 = 1;
|
|
676
645
|
let depth: u32 = 0;
|
|
677
646
|
for (let pos: u32 = 0; pos < <u32>data.length; pos++) {
|
|
678
|
-
char = unsafeCharCodeAt(data, pos);
|
|
647
|
+
const char = unsafeCharCodeAt(data, pos);
|
|
679
648
|
if (char === leftBraceCode) {
|
|
680
649
|
if (depth === 0) {
|
|
681
650
|
lastPos = pos;
|
package/assembly/src/util.ts
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
import { StringSink } from "as-string-sink/assembly";
|
|
2
|
-
import {
|
|
2
|
+
import { isSpace } from "util/string";
|
|
3
3
|
import { backSlashCode, quoteCode } from "./chars";
|
|
4
4
|
|
|
5
|
-
// @ts-ignore
|
|
6
|
-
@inline
|
|
7
|
-
export function unsafeCharCodeAt(data: string, pos: i32): i32 {
|
|
5
|
+
// @ts-ignore: Decorator
|
|
6
|
+
@inline export function unsafeCharCodeAt(data: string, pos: i32): i32 {
|
|
8
7
|
return load<u16>(changetype<usize>(data) + ((<usize>pos) << 1));
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
// @ts-ignore
|
|
12
|
-
@inline
|
|
13
|
-
export function removeWhitespace(data: string): string {
|
|
10
|
+
// @ts-ignore: Decorator
|
|
11
|
+
@inline export function removeWhitespace(data: string): string {
|
|
14
12
|
const result = new StringSink();
|
|
15
13
|
let instr = false;
|
|
16
14
|
for (let i = 0; i < data.length; i++) {
|
|
17
15
|
const char = data.charCodeAt(i);
|
|
18
16
|
if (instr === false && char === quoteCode) instr = true;
|
|
19
17
|
else if (
|
|
20
|
-
instr === true &&
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
instr = false;
|
|
18
|
+
instr === true && char === quoteCode
|
|
19
|
+
&& data.charCodeAt(i - 1) !== backSlashCode
|
|
20
|
+
) instr = false;
|
|
25
21
|
|
|
26
22
|
if (instr === false) {
|
|
27
23
|
if (!isSpace(char)) result.write(data.charAt(i));
|
|
@@ -32,9 +28,8 @@ export function removeWhitespace(data: string): string {
|
|
|
32
28
|
return result.toString();
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
// @ts-ignore
|
|
36
|
-
@inline
|
|
37
|
-
export function escapeChar(char: string): string {
|
|
31
|
+
// @ts-ignore: Decorator
|
|
32
|
+
@inline export function escapeChar(char: string): string {
|
|
38
33
|
switch (unsafeCharCodeAt(char, 0)) {
|
|
39
34
|
case 0x22:
|
|
40
35
|
return '\\"';
|
|
@@ -63,143 +58,169 @@ export function escapeChar(char: string): string {
|
|
|
63
58
|
* @returns depth of array
|
|
64
59
|
*/
|
|
65
60
|
|
|
66
|
-
// @ts-ignore
|
|
67
|
-
@inline
|
|
68
|
-
export function getArrayDepth<T>(depth: i32 = 1): i32 {
|
|
69
|
-
// @ts-ignore
|
|
61
|
+
// @ts-ignore: Decorator
|
|
62
|
+
@inline export function getArrayDepth<T extends ArrayLike>(depth: i32 = 1): i32 {
|
|
70
63
|
if (!isArray<T>()) {
|
|
71
64
|
return 0;
|
|
72
|
-
// @ts-ignore
|
|
73
65
|
} else if (isArray<valueof<T>>()) {
|
|
74
66
|
depth++;
|
|
75
|
-
// @ts-ignore
|
|
76
67
|
return getArrayDepth<valueof<T>>(depth);
|
|
77
68
|
} else {
|
|
78
69
|
return depth;
|
|
79
70
|
}
|
|
80
71
|
}
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
73
|
+
/** Scientific Notation Integer Parsing - SNIP
|
|
74
|
+
* This is absolutely the fastest algorithm I could think of while adding full support for Scientific Notation
|
|
75
|
+
* Loads 32 bits and retrieves the high/low bits.
|
|
76
|
+
* The reason why we only load 4 bytes at a time is that numbers in the 32-bit range are 7 chars long at most.
|
|
77
|
+
* Using SIMD or 64 bit loads would only work well when parsing large 128+ numbers.
|
|
78
|
+
*
|
|
79
|
+
* Here are some benchmarks
|
|
80
|
+
* Parsing: "12345"
|
|
81
|
+
* Results are spread over 5000ms
|
|
82
|
+
*
|
|
83
|
+
* SNIP: 270M iterations
|
|
84
|
+
* ATOI: 285M iterations
|
|
85
|
+
* ParseInt: 176M iterations
|
|
86
|
+
*
|
|
87
|
+
* @param str - Any number. Can include scientific notation.
|
|
88
|
+
*/
|
|
89
|
+
// @ts-ignore: Decorator
|
|
90
|
+
@inline export function snip_fast<T extends number>(str: string, len: u32 = 0, offset: u32 = 0): T {
|
|
91
|
+
if (isSigned<T>()) {
|
|
92
|
+
const firstChar: u32 = load<u16>(changetype<usize>(str));
|
|
93
|
+
if (firstChar === 48) return 0 as T;
|
|
94
|
+
const isNegative = firstChar === 45; // Check if the number is negative
|
|
95
|
+
let val: T = 0 as T;
|
|
96
|
+
if (len == 0) len = u32(str.length << 1);
|
|
97
|
+
if (isNegative) {
|
|
98
|
+
offset += 2;
|
|
99
|
+
if (len >= 4) {
|
|
100
|
+
// 32-bit route
|
|
101
|
+
for (; offset < (len - 3); offset += 4) {
|
|
102
|
+
const ch = load<u32>(changetype<usize>(str) + <usize>offset);
|
|
103
|
+
const low = ch & 0xFFFF;
|
|
104
|
+
const high = ch >> 16;
|
|
105
|
+
// 9 is 57. The highest group of two numbers is 114, so if a e or an E is included, this will fire.
|
|
106
|
+
if (low > 57) {
|
|
107
|
+
// The first char (f) is E or e
|
|
108
|
+
// We push the offset up by two and apply the notation.
|
|
109
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 2) == 45) {
|
|
110
|
+
return -(val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
111
|
+
} else {
|
|
112
|
+
// Inlined this operation instead of using a loop
|
|
113
|
+
return -(val * (10 ** (atoi_fast<u32>(str, offset + 2) + 1))) as T;
|
|
116
114
|
}
|
|
117
|
-
} else {
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
} else if (high > 57) {
|
|
116
|
+
// The first char (f) is E or e
|
|
117
|
+
// We push the offset up by two and apply the notation.
|
|
118
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 4) == 45) {
|
|
119
|
+
return -(val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
120
|
+
} else {
|
|
121
|
+
// Inlined this operation instead of using a loop
|
|
122
|
+
return -(val * (10 ** (atoi_fast<u32>(str, offset + 4) + 1))) as T;
|
|
120
123
|
}
|
|
124
|
+
} else {
|
|
125
|
+
val = (val * 100 + ((low - 48) * 10) + (high - 48)) as T;
|
|
121
126
|
}
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Finish up the remainder with 16 bits.
|
|
130
|
+
for (; offset < len; offset += 2) {
|
|
131
|
+
const ch = load<u16>(changetype<usize>(str) + <usize>offset);
|
|
132
|
+
// 9 is 57. E and e are larger. Assumes valid JSON.
|
|
133
|
+
if (ch > 57) {
|
|
124
134
|
// The first char (f) is E or e
|
|
125
135
|
// We push the offset up by two and apply the notation.
|
|
126
|
-
offset
|
|
127
|
-
|
|
128
|
-
if (exp < 0) {
|
|
129
|
-
for (let i = 0; i < exp; i++) {
|
|
130
|
-
val = (val / 10) as T;
|
|
131
|
-
}
|
|
136
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 2) == 45) {
|
|
137
|
+
return -(val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
132
138
|
} else {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
139
|
+
// Inlined this operation instead of using a loop
|
|
140
|
+
return -(val * (10 ** (atoi_fast<u32>(str, offset + 2) + 1))) as T;
|
|
136
141
|
}
|
|
137
|
-
return -val as T;
|
|
138
142
|
} else {
|
|
139
|
-
val = (val *
|
|
143
|
+
val = (val * 10) + (ch - 48) as T;
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
return -val as T;
|
|
147
|
+
} else {
|
|
148
|
+
if (len >= 4) {
|
|
149
|
+
// Duplet 16 bit lane load
|
|
150
|
+
for (; offset < (len - 3); offset += 4) {
|
|
151
|
+
const ch = load<u32>(changetype<usize>(str) + <usize>offset);
|
|
152
|
+
const low = ch & 0xFFFF;
|
|
153
|
+
const high = ch >> 16;
|
|
154
|
+
// 9 is 57. The highest group of two numbers is 114, so if a e or an E is included, this will fire.
|
|
155
|
+
if (low > 57) {
|
|
156
|
+
// The first char (f) is E or e
|
|
157
|
+
// We push the offset up by two and apply the notation.
|
|
158
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 2) == 45) {
|
|
159
|
+
return (val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
160
|
+
} else {
|
|
161
|
+
// Inlined this operation instead of using a loop
|
|
162
|
+
return (val * (10 ** (atoi_fast<u32>(str, offset + 2) + 1))) as T;
|
|
163
|
+
}
|
|
164
|
+
} else if (high > 57) {
|
|
165
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 4) == 45) {
|
|
166
|
+
return (val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
167
|
+
} else {
|
|
168
|
+
// Inlined this operation instead of using a loop
|
|
169
|
+
return (val * (10 ** (atoi_fast<u32>(str, offset + 4) + 1))) as T;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// Optimized with multiplications and shifts.
|
|
173
|
+
val = (val * 100 + ((low - 48) * 10) + (high - 48)) as T;
|
|
155
174
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Cover the remaining numbers with 16 bit loads.
|
|
178
|
+
for (; offset < len; offset += 2) {
|
|
179
|
+
const ch = load<u16>(changetype<usize>(str) + <usize>offset);
|
|
180
|
+
// 0's char is 48 and 9 is 57. Anything above this range would signify an exponent (e or E).
|
|
181
|
+
// e is 101 and E is 69.
|
|
182
|
+
if (ch > 57) {
|
|
183
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 2) == 45) {
|
|
184
|
+
val = (val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
185
|
+
} else {
|
|
186
|
+
// Inlined this operation instead of using a loop
|
|
187
|
+
val = (val * (10 ** (atoi_fast<u32>(str, offset + 2) + 1))) as T;
|
|
159
188
|
}
|
|
189
|
+
return val as T;
|
|
190
|
+
} else {
|
|
191
|
+
val = (val * 10) + (ch - 48) as T;
|
|
160
192
|
}
|
|
161
|
-
return -val as T;
|
|
162
|
-
} else {
|
|
163
|
-
val = (val * 10) + (ch - 48) as T;
|
|
164
193
|
}
|
|
194
|
+
return val as T;
|
|
165
195
|
}
|
|
166
|
-
return -val as T;
|
|
167
196
|
} else {
|
|
197
|
+
const firstChar: u32 = load<u16>(changetype<usize>(str));
|
|
198
|
+
if (firstChar === 48) return 0 as T;
|
|
199
|
+
let val: T = 0 as T;
|
|
200
|
+
if (len == 0) len = u32(str.length << 1);
|
|
168
201
|
if (len >= 4) {
|
|
169
202
|
// Duplet 16 bit lane load
|
|
170
203
|
for (; offset < (len - 3); offset += 4) {
|
|
171
|
-
ch = load<u32>(changetype<usize>(str) + <usize>offset);
|
|
204
|
+
const ch = load<u32>(changetype<usize>(str) + <usize>offset);
|
|
172
205
|
const low = ch & 0xFFFF;
|
|
173
206
|
const high = ch >> 16;
|
|
174
207
|
// 9 is 57. The highest group of two numbers is 114, so if a e or an E is included, this will fire.
|
|
175
208
|
if (low > 57) {
|
|
176
209
|
// The first char (f) is E or e
|
|
177
210
|
// We push the offset up by two and apply the notation.
|
|
178
|
-
offset
|
|
179
|
-
|
|
180
|
-
if (exp < 0) {
|
|
181
|
-
for (let i = 0; i < exp; i++) {
|
|
182
|
-
val = (val / 10) as T;
|
|
183
|
-
}
|
|
211
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 2) == 45) {
|
|
212
|
+
return (val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
184
213
|
} else {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
214
|
+
// Inlined this operation instead of using a loop
|
|
215
|
+
return (val * (10 ** (atoi_fast<u32>(str, offset + 2) + 1))) as T;
|
|
188
216
|
}
|
|
189
|
-
return val as T;
|
|
190
217
|
} else if (high > 57) {
|
|
191
|
-
offset
|
|
192
|
-
|
|
193
|
-
if (exp < 0) {
|
|
194
|
-
for (let i = 0; i < exp; i++) {
|
|
195
|
-
val = (val / 10) as T;
|
|
196
|
-
}
|
|
218
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 4) == 45) {
|
|
219
|
+
return (val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
197
220
|
} else {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
221
|
+
// Inlined this operation instead of using a loop
|
|
222
|
+
return (val * (10 ** (atoi_fast<u32>(str, offset + 4) + 1))) as T;
|
|
201
223
|
}
|
|
202
|
-
return val as T;
|
|
203
224
|
} else {
|
|
204
225
|
// Optimized with multiplications and shifts.
|
|
205
226
|
val = (val * 100 + ((low - 48) * 10) + (high - 48)) as T;
|
|
@@ -208,22 +229,16 @@ export function snip_fast<T extends number>(str: string, offset: u32 = 0): T {
|
|
|
208
229
|
}
|
|
209
230
|
// Cover the remaining numbers with 16 bit loads.
|
|
210
231
|
for (; offset < len; offset += 2) {
|
|
211
|
-
ch = load<u16>(changetype<usize>(str) + <usize>offset);
|
|
232
|
+
const ch = load<u16>(changetype<usize>(str) + <usize>offset);
|
|
212
233
|
// 0's char is 48 and 9 is 57. Anything above this range would signify an exponent (e or E).
|
|
213
234
|
// e is 101 and E is 69.
|
|
214
235
|
if (ch > 57) {
|
|
215
|
-
offset
|
|
216
|
-
|
|
217
|
-
if (exp < 0) {
|
|
218
|
-
for (let i = 0; i > exp; i--) {
|
|
219
|
-
val = (val / 10) as T;
|
|
220
|
-
}
|
|
236
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset + 2) == 45) {
|
|
237
|
+
return (val / (10 ** (atoi_fast<u32>(str, offset + 6) - 1))) as T;
|
|
221
238
|
} else {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
239
|
+
// Inlined this operation instead of using a loop
|
|
240
|
+
return (val * (10 ** (atoi_fast<u32>(str, offset + 2) + 1))) as T;
|
|
225
241
|
}
|
|
226
|
-
return val as T;
|
|
227
242
|
} else {
|
|
228
243
|
val = (val * 10) + (ch - 48) as T;
|
|
229
244
|
}
|
|
@@ -234,29 +249,32 @@ export function snip_fast<T extends number>(str: string, offset: u32 = 0): T {
|
|
|
234
249
|
|
|
235
250
|
/**
|
|
236
251
|
* Implementation of ATOI. Can be much much faster with SIMD.
|
|
237
|
-
* Benchmark: 40-46m ops/s
|
|
238
252
|
*/
|
|
239
253
|
|
|
240
254
|
// @ts-ignore
|
|
241
|
-
@inline
|
|
242
|
-
export function atoi_fast<T extends number>(str: string, offset: u32 = 0): T {
|
|
255
|
+
@inline export function atoi_fast<T extends number>(str: string, offset: u32 = 0): T {
|
|
243
256
|
// @ts-ignore
|
|
244
257
|
let val: T = 0;
|
|
245
258
|
const len = u32(str.length << 1);
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
259
|
+
if (isSigned<T>()) {
|
|
260
|
+
// Negative path
|
|
261
|
+
if (load<u16>(changetype<usize>(str) + <usize>offset) === 45) {
|
|
262
|
+
offset += 2;
|
|
263
|
+
for (; offset < len; offset += 2) {
|
|
264
|
+
val = (val * 10) + (load<u16>(changetype<usize>(str) + <usize>offset) - 48) as T;
|
|
265
|
+
}
|
|
266
|
+
return -val as T;
|
|
267
|
+
} else {
|
|
268
|
+
for (; offset < len; offset += 2) {
|
|
269
|
+
val = ((val * 10) + (load<u16>(changetype<usize>(str) + <usize>offset) - 48)) as T;
|
|
270
|
+
}
|
|
271
|
+
return val as T;
|
|
251
272
|
}
|
|
252
|
-
// @ts-ignore
|
|
253
|
-
return -val;
|
|
254
273
|
} else {
|
|
255
274
|
for (; offset < len; offset += 2) {
|
|
256
|
-
|
|
257
|
-
val = (val << 1) + (val << 3) + (load<u16>(changetype<usize>(str) + <usize>offset) - 48);
|
|
275
|
+
val = ((val * 10) + (load<u16>(changetype<usize>(str) + <usize>offset) - 48)) as T;
|
|
258
276
|
}
|
|
259
|
-
return val;
|
|
277
|
+
return val as T;
|
|
260
278
|
}
|
|
261
279
|
}
|
|
262
280
|
|
|
@@ -269,8 +287,7 @@ export function atoi_fast<T extends number>(str: string, offset: u32 = 0): T {
|
|
|
269
287
|
*/
|
|
270
288
|
|
|
271
289
|
// @ts-ignore
|
|
272
|
-
@inline
|
|
273
|
-
export function parseSciInteger<T extends number>(str: string): T {
|
|
290
|
+
@inline export function parseSciInteger<T extends number>(str: string): T {
|
|
274
291
|
// @ts-ignore
|
|
275
292
|
let val: T = 0;
|
|
276
293
|
let offset = 0;
|
|
@@ -299,22 +316,21 @@ export function parseSciInteger<T extends number>(str: string): T {
|
|
|
299
316
|
// We use load because in this case, there is no need to have bounds-checking
|
|
300
317
|
}
|
|
301
318
|
if (firstChar === 45) {
|
|
302
|
-
val = -val;
|
|
319
|
+
val = -val as T;
|
|
303
320
|
}
|
|
304
321
|
return val;
|
|
305
322
|
}
|
|
306
323
|
|
|
307
324
|
// @ts-ignore
|
|
308
|
-
@inline
|
|
309
|
-
function sciNote<T extends number>(num: T): T {
|
|
325
|
+
@inline function sciNote<T extends number>(num: T): T {
|
|
310
326
|
let res = 1;
|
|
311
327
|
// @ts-ignore
|
|
312
328
|
if (num > 0) {
|
|
313
|
-
for (let i: T = 0; i < num; i++) {
|
|
329
|
+
for (let i: T = <T>0; i < num; i++) {
|
|
314
330
|
res *= 10;
|
|
315
331
|
}
|
|
316
332
|
} else {
|
|
317
|
-
for (let i: T = 0; i < num; i++) {
|
|
333
|
+
for (let i: T = <T>0; i < num; i++) {
|
|
318
334
|
res /= 10;
|
|
319
335
|
}
|
|
320
336
|
}
|
package/assembly/test.ts
CHANGED
|
@@ -1,89 +1,6 @@
|
|
|
1
|
-
import { snip_fast } from "./src/util";
|
|
2
|
-
|
|
3
1
|
import { JSON } from "./src/json";
|
|
4
|
-
@json
|
|
5
|
-
class Vec3 {
|
|
6
|
-
x!: f64;
|
|
7
|
-
y!: f64;
|
|
8
|
-
z!: f64;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
@json
|
|
12
|
-
class Player {
|
|
13
|
-
firstName!: string;
|
|
14
|
-
lastName!: string;
|
|
15
|
-
lastActive!: i32[];
|
|
16
|
-
age!: i32;
|
|
17
|
-
pos!: Vec3 | null;
|
|
18
|
-
isVerified!: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const player: Player = {
|
|
22
|
-
firstName: "Emmet",
|
|
23
|
-
lastName: "West",
|
|
24
|
-
lastActive: [8, 27, 2022],
|
|
25
|
-
age: 23,
|
|
26
|
-
pos: {
|
|
27
|
-
x: 3.4,
|
|
28
|
-
y: 1.2,
|
|
29
|
-
z: 8.3,
|
|
30
|
-
},
|
|
31
|
-
isVerified: true,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const vec: Vec3 = {
|
|
35
|
-
x: 3,
|
|
36
|
-
y: 1,
|
|
37
|
-
z: 8,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
function canSerde<T>(data: T, toBe: string = ""): void {
|
|
41
|
-
if (!toBe) toBe = JSON.stringify<T>(data);
|
|
42
|
-
const deserialized = JSON.stringify<T>(JSON.parse<T>(JSON.stringify(data)));
|
|
43
|
-
if (deserialized != toBe) {
|
|
44
|
-
console.log("Expected: " + toBe);
|
|
45
|
-
console.log("Actual: " + deserialized);
|
|
46
|
-
} else {
|
|
47
|
-
console.log("Passed Test")
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
canSerde<Player>({
|
|
52
|
-
firstName: "Emmet",
|
|
53
|
-
lastName: "West",
|
|
54
|
-
lastActive: [8, 27, 2022],
|
|
55
|
-
age: 23,
|
|
56
|
-
pos: {
|
|
57
|
-
x: 3.4,
|
|
58
|
-
y: 1.2,
|
|
59
|
-
z: 8.3,
|
|
60
|
-
},
|
|
61
|
-
isVerified: true,
|
|
62
|
-
}, '{"firstName":"Emmet","lastName":"West","lastActive":[8,27,2022],"age":23,"pos":{"x":3.4,"y":1.2,"z":8.3},"isVerified":true}');
|
|
63
|
-
const serializedPlayer = JSON.stringify<Player>(player);
|
|
64
|
-
console.log(serializedPlayer);
|
|
65
|
-
const parsedPlayer = JSON.parse<Player>(serializedPlayer);
|
|
66
|
-
console.log(JSON.stringify(parsedPlayer));
|
|
67
|
-
|
|
68
|
-
const serializedVec3 = JSON.stringify(vec);
|
|
69
|
-
console.log(serializedVec3);
|
|
70
|
-
|
|
71
|
-
const parsedVec3 = JSON.parse<Vec3>(serializedVec3);
|
|
72
|
-
console.log(JSON.stringify(parsedVec3));
|
|
73
2
|
|
|
74
|
-
console.log("
|
|
75
|
-
console.log(
|
|
76
|
-
console.log(
|
|
77
|
-
console.log(
|
|
78
|
-
console.log("123400 <-> " + snip_fast<i32>("1234e2").toString());
|
|
79
|
-
console.log("1234000 <-> " + snip_fast<i32>("1234e3").toString());
|
|
80
|
-
console.log("12 <-> " + snip_fast<i32>("123e-1").toString());
|
|
81
|
-
console.log("123 <-> " + snip_fast<i32>("123").toString());
|
|
82
|
-
console.log("-12340 <-> " + snip_fast<i32>("-1234e1").toString());
|
|
83
|
-
console.log("-1234500 <-> " + snip_fast<i32>("-12345e2").toString());
|
|
84
|
-
console.log("-123456000 <-> " + snip_fast<i32>("-123456e3").toString());
|
|
85
|
-
console.log("-12 <-> " + snip_fast<i32>("-123e-1").toString());
|
|
86
|
-
console.log("-123 <-> " + snip_fast<i32>("-123").toString());
|
|
87
|
-
console.log(snip_fast<i32>("1000").toString());
|
|
88
|
-
console.log(snip_fast<i32>("-1000").toString());
|
|
89
|
-
console.log(JSON.stringify([[1, 2, 3, 4, 5], [5, 4, 3, 2, 1]]));
|
|
3
|
+
console.log(JSON.parse<u32>("100").toString());
|
|
4
|
+
console.log(JSON.stringify<u32>(100));
|
|
5
|
+
console.log(JSON.stringify(JSON.parse<u32>(JSON.stringify(100))))
|
|
6
|
+
console.log((100).toString())
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Comparison between integer parsing algorithms
|
|
2
|
+
|
|
3
|
+
SNIP: 261M iterations over 5000ms
|
|
4
|
+
ATOI: 285M iterations over 5000ms
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### Log
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
yarn run v1.22.19
|
|
11
|
+
$ astral -Ospeed --noAssert --uncheckedBehavior always --runtime stub
|
|
12
|
+
Compiling assembly/__benches__/as-json.ts
|
|
13
|
+
|
|
14
|
+
Benchmarking Parse Number SNIP: Warming up for 3000ms
|
|
15
|
+
Benchmarking Parse Number SNIP: Collecting 100 samples in estimated 5000ms (261M iterations)
|
|
16
|
+
Benchmarking Parse Number SNIP: Analyzing
|
|
17
|
+
Parse Number SNIP time: [18.146ns 18.381ns 18.624ns]
|
|
18
|
+
change: [-13.073% -10.614% -8.1347%] (p = 0.00 < 0.05)
|
|
19
|
+
Performance has improved.
|
|
20
|
+
Found 9 outliers among 100 measurements (9%)
|
|
21
|
+
8 (8%) high mild
|
|
22
|
+
1 (1%) high severe
|
|
23
|
+
|
|
24
|
+
Benchmarking Parse Number ATOI: Warming up for 3000ms
|
|
25
|
+
Benchmarking Parse Number ATOI: Collecting 100 samples in estimated 5000ms (285M iterations)
|
|
26
|
+
Benchmarking Parse Number ATOI: Analyzing
|
|
27
|
+
Parse Number ATOI time: [16.962ns 17.219ns 17.501ns]
|
|
28
|
+
change: [-3.5659% -0.8496% +2.0516%] (p = 0.00 < 0.05)
|
|
29
|
+
Change within noise threshold.
|
|
30
|
+
Found 7 outliers among 100 measurements (7%)
|
|
31
|
+
2 (2%) high mild
|
|
32
|
+
5 (5%) high severe
|
|
33
|
+
|
|
34
|
+
Benchmarking Parse Number STDLIB: Warming up for 3000ms
|
|
35
|
+
Benchmarking Parse Number STDLIB: Collecting 100 samples in estimated 5000ms (176M iterations)
|
|
36
|
+
Benchmarking Parse Number STDLIB: Analyzing
|
|
37
|
+
Parse Number STDLIB time: [28.298ns 28.763ns 29.383ns]
|
|
38
|
+
change: [-3.3724% -1.5367% +0.1796%] (p = 0.00 < 0.05)
|
|
39
|
+
Change within noise threshold.
|
|
40
|
+
Found 5 outliers among 100 measurements (5%)
|
|
41
|
+
3 (3%) high mild
|
|
42
|
+
2 (2%) high severe
|
|
43
|
+
|
|
44
|
+
Benchmarking Parse Number OLD: Warming up for 3000ms
|
|
45
|
+
Benchmarking Parse Number OLD: Collecting 100 samples in estimated 5000ms (171M iterations)
|
|
46
|
+
Benchmarking Parse Number OLD: Analyzing
|
|
47
|
+
Parse Number OLD time: [28.888ns 29.341ns 29.804ns]
|
|
48
|
+
change: [-2.123% -0.5458% +1.1939%] (p = 0.00 < 0.05)
|
|
49
|
+
Change within noise threshold.
|
|
50
|
+
Found 3 outliers among 100 measurements (3%)
|
|
51
|
+
3 (3%) high mild
|
|
52
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-as",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.54",
|
|
4
4
|
"description": "JSON encoder/decoder for AssemblyScript",
|
|
5
5
|
"types": "assembly/index.ts",
|
|
6
6
|
"author": "Jairus Tanaka",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
],
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"scripts": {
|
|
16
|
-
"aspect": "asp",
|
|
17
|
-
"bench:astral": "astral
|
|
16
|
+
"test:aspect": "asp",
|
|
17
|
+
"bench:astral": "astral --optimizeLevel 3 --shrinkLevel 0 --converge --noAssert --uncheckedBehavior never --runtime stub",
|
|
18
18
|
"build:test": "asc assembly/test.ts --target test",
|
|
19
19
|
"build:transform": "tsc -p ./transform",
|
|
20
20
|
"test:wasmtime": "wasmtime ./build/test.wasm",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"@as-tral/cli": "^2.0.0",
|
|
28
28
|
"@assemblyscript/wasi-shim": "^0.1.0",
|
|
29
29
|
"assemblyscript": "^0.27.8",
|
|
30
|
-
"assemblyscript-prettier": "^
|
|
30
|
+
"assemblyscript-prettier": "^2.0.2",
|
|
31
31
|
"benchmark": "^2.1.4",
|
|
32
32
|
"kati": "^0.6.2",
|
|
33
33
|
"microtime": "^3.1.1",
|
|
34
|
-
"prettier": "^
|
|
34
|
+
"prettier": "^3.0.1",
|
|
35
35
|
"tinybench": "^2.5.0",
|
|
36
|
-
"typescript": "^
|
|
36
|
+
"typescript": "^5.1.6",
|
|
37
37
|
"visitor-as": "^0.11.4"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
package/transform/package.json
CHANGED
package/asconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"targets": {
|
|
3
|
-
"test": {
|
|
4
|
-
"outFile": "build/test.wasm",
|
|
5
|
-
"sourceMap": false,
|
|
6
|
-
"optimizeLevel": 0,
|
|
7
|
-
"shrinkLevel": 0,
|
|
8
|
-
"converge": false,
|
|
9
|
-
"noAssert": false
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"options": {
|
|
13
|
-
"transform": ["./transform"],
|
|
14
|
-
"bindings": "esm"}
|
|
15
|
-
}
|