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.
@@ -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 { backSlashCode, quoteCode } from "../src/chars";
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
- /*bench("Parse Object (Vec3)", () => {
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", () => {
@@ -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
- // @ts-ignore
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;
@@ -1,27 +1,23 @@
1
1
  import { StringSink } from "as-string-sink/assembly";
2
- import { CharCode, isSpace } from "util/string";
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
- char === quoteCode &&
22
- data.charCodeAt(i - 1) !== backSlashCode
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
- // Scientific Notation Integer Parsing - SNIP
83
- // This is absolutely the fastest algorithm I could think of while adding full support for Scientific Notation
84
- // Loads 32 bits and retrieves the high/low bits
85
- // Here are some benchmarks
86
- // Parsing: "12345"
87
- // Results are spread over 5000ms
88
- // SNIP: 207M iterations
89
- // ATOI: 222M iterations
90
- // STD (parseInt): 162M iterations
91
- export function snip_fast<T extends number>(str: string, offset: u32 = 0): T {
92
- let ch: u32 = load<u32>(changetype<usize>(str));
93
- const h = ch & 0xFFFF;
94
- if (h === 48) return 0 as T;
95
- const isNegative = h === 45; // Check if the number is negative
96
- let val: T = 0 as T;
97
- const len = u32(str.length << 1);
98
- if (isNegative) {
99
- if ((ch >> 16) === 48) return -0 as T;
100
- offset += 2;
101
- if (len >= 4) {
102
- // 32-bit route
103
- for (; offset < (len - 3); offset += 4) {
104
- ch = load<u32>(changetype<usize>(str) + <usize>offset);
105
- const low = ch & 0xFFFF;
106
- const high = ch >> 16;
107
- // 9 is 57. The highest group of two numbers is 114, so if a e or an E is included, this will fire.
108
- if (low > 57) {
109
- // The first char (f) is E or e
110
- // We push the offset up by two and apply the notation.
111
- offset += 2;
112
- let exp: i32 = atoi_fast<i32>(str, offset);
113
- if (exp < 0) {
114
- for (let i = 0; i < exp; i++) {
115
- val = (val / 10) as T;
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
- for (let i = 0; i < exp; i++) {
119
- val = (val * 10) as T;
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
- return -val as T;
123
- } else if (high > 57) {
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 += 4;
127
- let exp: i32 = atoi_fast<i32>(str, offset);
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
- for (let i = 0; i < exp; i++) {
134
- val = (val * 10) as T;
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 * 100 + ((low - 48) * 10) + (high - 48)) as T;
143
+ val = (val * 10) + (ch - 48) as T;
140
144
  }
141
145
  }
142
- }
143
- // Finish up the remainder with 16 bits.
144
- for (; offset < len; offset += 2) {
145
- ch = load<u16>(changetype<usize>(str) + <usize>offset);
146
- // 9 is 57. E and e are larger. Assumes valid JSON.
147
- if (ch > 57) {
148
- // The first char (f) is E or e
149
- // We push the offset up by two and apply the notation.
150
- offset += 2;
151
- let exp: i32 = atoi_fast<i32>(str, offset);
152
- if (exp < 0) {
153
- for (let i = 0; i > exp; i--) {
154
- val = (val / 10) as T;
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
- } else {
157
- for (let i = 0; i < exp; i++) {
158
- val = (val * 10) as T;
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 += 2;
179
- let exp: i32 = atoi_fast<i32>(str, offset);
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
- for (let i = 0; i < exp; i++) {
186
- val = (val * 10) as T;
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 += 4;
192
- let exp: i32 = atoi_fast<i32>(str, offset);
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
- for (let i = 0; i < exp; i++) {
199
- val = (val * 10) as T;
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 += 2;
216
- let exp: i32 = atoi_fast<i32>(str, offset);
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
- for (let i = 0; i < exp; i++) {
223
- val = (val * 10) as T;
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 (load<u16>(changetype<usize>(str) + <usize>offset) === 45) {
247
- offset += 2;
248
- for (; offset < len; offset += 2) {
249
- // @ts-ignore
250
- val = (val << 1) + (val << 3) + (load<u16>(changetype<usize>(str) + <usize>offset) - 48);
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
- // @ts-ignore
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("snip:");
75
- console.log("1234 <-> " + snip_fast<i32>("1234").toString());
76
- console.log("-1234 <-> " + snip_fast<i32>("-1234").toString());
77
- console.log("12340 <-> " + snip_fast<i32>("1234e1").toString());
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.52",
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 -Ospeed --noAssert --uncheckedBehavior always --runtime stub",
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": "^1.0.7",
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": "^2.8.4",
34
+ "prettier": "^3.0.1",
35
35
  "tinybench": "^2.5.0",
36
- "typescript": "^4.9.5",
36
+ "typescript": "^5.1.6",
37
37
  "visitor-as": "^0.11.4"
38
38
  },
39
39
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@json-as/transform",
3
- "version": "0.5.52",
3
+ "version": "0.5.54",
4
4
  "description": "JSON encoder/decoder for AssemblyScript",
5
5
  "main": "./lib/index.js",
6
6
  "author": "Jairus Tanaka",
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
- }