json-as 0.7.2 → 0.7.3
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/assembly/__tests__/as-json.spec.ts +298 -2
- package/assembly/src/chars.ts +12 -1
- package/assembly/src/json.ts +133 -156
- package/assembly/src/util.ts +11 -3
- package/assembly/test.ts +2 -2
- package/package.json +1 -1
- package/transform/lib/index.js +53 -34
- package/transform/package.json +1 -1
- package/transform/src/index.ts +65 -37
|
@@ -73,9 +73,18 @@ describe("Ser/de Numbers", () => {
|
|
|
73
73
|
canSerde<f64>(10e2, "1000.0");
|
|
74
74
|
|
|
75
75
|
canSerde<f64>(123456e-5, "1.23456");
|
|
76
|
-
|
|
77
76
|
canSerde<f64>(0.0, "0.0");
|
|
78
|
-
canSerde<f64>(7.23, "7.23");
|
|
77
|
+
canSerde<f64>(-7.23, "-7.23");
|
|
78
|
+
|
|
79
|
+
canSerde<f64>(1e-6, "0.000001");
|
|
80
|
+
canSerde<f64>(1e-7, "1e-7");
|
|
81
|
+
canDeser<f64>("1E-7", 1e-7);
|
|
82
|
+
|
|
83
|
+
canSerde<f64>(1e20, "100000000000000000000.0");
|
|
84
|
+
canSerde<f64>(1e21, "1e+21");
|
|
85
|
+
canDeser<f64>("1E+21", 1e21);
|
|
86
|
+
canDeser<f64>("1e21", 1e21);
|
|
87
|
+
canDeser<f64>("1E21", 1e21);
|
|
79
88
|
});
|
|
80
89
|
|
|
81
90
|
it("should ser/de booleans", () => {
|
|
@@ -97,6 +106,11 @@ describe("Ser/de Array", () => {
|
|
|
97
106
|
|
|
98
107
|
it("should ser/de float arrays", () => {
|
|
99
108
|
canSerde<f64[]>([7.23, 10e2, 10e2, 123456e-5, 123456e-5, 0.0, 7.23]);
|
|
109
|
+
|
|
110
|
+
canSerde<f64[]>([1e21,1e22,1e-7,1e-8,1e-9], "[1e+21,1e+22,1e-7,1e-8,1e-9]");
|
|
111
|
+
canDeser<f64[]>("[1E+21,1E+22,1E-7,1E-8,1E-9]", [1e21,1e22,1e-7,1e-8,1e-9]);
|
|
112
|
+
canDeser<f64[]>("[1e21,1e22,1e-7,1e-8,1e-9]", [1e21,1e22,1e-7,1e-8,1e-9]);
|
|
113
|
+
canDeser<f64[]>("[1E21,1E22,1E-7,1E-8,1E-9]", [1e21,1e22,1e-7,1e-8,1e-9]);
|
|
100
114
|
});
|
|
101
115
|
|
|
102
116
|
it("should ser/de boolean arrays", () => {
|
|
@@ -167,6 +181,38 @@ describe("Ser/de Objects", () => {
|
|
|
167
181
|
isVerified: true,
|
|
168
182
|
}, '{"firstName":"Emmet","lastName":"West","lastActive":[8,27,2022],"age":23,"pos":{"x":3.4,"y":1.2,"z":8.3},"isVerified":true}');
|
|
169
183
|
});
|
|
184
|
+
|
|
185
|
+
it("should ser/de object with floats", () => {
|
|
186
|
+
canSerde<ObjectWithFloat>({ f: 7.23 }, '{"f":7.23}');
|
|
187
|
+
canSerde<ObjectWithFloat>({ f: 0.000001 }, '{"f":0.000001}');
|
|
188
|
+
|
|
189
|
+
canSerde<ObjectWithFloat>({ f: 1e-7 }, '{"f":1e-7}');
|
|
190
|
+
canDeser<ObjectWithFloat>('{"f":1E-7}', { f: 1e-7 });
|
|
191
|
+
|
|
192
|
+
canSerde<ObjectWithFloat>({ f: 1e20 }, '{"f":100000000000000000000.0}');
|
|
193
|
+
canSerde<ObjectWithFloat>({ f: 1e21 }, '{"f":1e+21}');
|
|
194
|
+
canDeser<ObjectWithFloat>('{"f":1E+21}', { f: 1e21 });
|
|
195
|
+
canDeser<ObjectWithFloat>('{"f":1e21}', { f: 1e21 });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should ser/de object with float arrays", () => {
|
|
199
|
+
canSerde<ObjectWithFloatArray>(
|
|
200
|
+
{ fa: [1e21,1e22,1e-7,1e-8,1e-9] },
|
|
201
|
+
'{"fa":[1e+21,1e+22,1e-7,1e-8,1e-9]}');
|
|
202
|
+
|
|
203
|
+
canDeser<ObjectWithFloatArray>(
|
|
204
|
+
'{"fa":[1E+21,1E+22,1E-7,1E-8,1E-9]}',
|
|
205
|
+
{ fa: [1e21,1e22,1e-7,1e-8,1e-9] });
|
|
206
|
+
|
|
207
|
+
canDeser<ObjectWithFloatArray>(
|
|
208
|
+
'{"fa":[1e21,1e22,1e-7,1e-8,1e-9]}',
|
|
209
|
+
{ fa: [1e21,1e22,1e-7,1e-8,1e-9] });
|
|
210
|
+
|
|
211
|
+
canDeser<ObjectWithFloatArray>(
|
|
212
|
+
'{"fa":[1E21,1E22,1E-7,1E-8,1E-9]}',
|
|
213
|
+
{ fa: [1e21,1e22,1e-7,1e-8,1e-9] });
|
|
214
|
+
|
|
215
|
+
});
|
|
170
216
|
});
|
|
171
217
|
|
|
172
218
|
describe("Ser externals", () => {
|
|
@@ -343,3 +389,253 @@ describe("Ser/de Maps", () => {
|
|
|
343
389
|
});
|
|
344
390
|
|
|
345
391
|
});
|
|
392
|
+
|
|
393
|
+
describe("Ser/de escape sequences in strings", () => {
|
|
394
|
+
it("should encode short escape sequences", () => {
|
|
395
|
+
canSer("\\", '"\\\\"');
|
|
396
|
+
canSer('"', '"\\""');
|
|
397
|
+
canSer("\n", '"\\n"');
|
|
398
|
+
canSer("\r", '"\\r"');
|
|
399
|
+
canSer("\t", '"\\t"');
|
|
400
|
+
canSer("\b", '"\\b"');
|
|
401
|
+
canSer("\f", '"\\f"');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("should decode short escape sequences", () => {
|
|
405
|
+
canDeser('"\\\\"', "\\");
|
|
406
|
+
canDeser('"\\""', '"');
|
|
407
|
+
canDeser('"\\n"', "\n");
|
|
408
|
+
canDeser('"\\r"', "\r");
|
|
409
|
+
canDeser('"\\t"', "\t");
|
|
410
|
+
canDeser('"\\b"', "\b");
|
|
411
|
+
canDeser('"\\f"', "\f");
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("should decode escaped forward slash but not encode", () => {
|
|
415
|
+
canSer("/", '"/"');
|
|
416
|
+
canDeser('"/"', "/");
|
|
417
|
+
canDeser('"\\/"', "/"); // allowed
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// 0x00 - 0x1f, excluding characters that have short escape sequences
|
|
421
|
+
it("should encode long escape sequences", () => {
|
|
422
|
+
const singles = ["\n", "\r", "\t", "\b", "\f"];
|
|
423
|
+
for (let i = 0; i < 0x1F; i++) {
|
|
424
|
+
const c = String.fromCharCode(i);
|
|
425
|
+
if (singles.includes(c)) continue;
|
|
426
|
+
const actual = JSON.stringify(c);
|
|
427
|
+
const expected = `"\\u${i.toString(16).padStart(4, "0")}"`;
|
|
428
|
+
expect(actual).toBe(expected, `Failed to encode '\\x${i.toString(16).padStart(2, "0")}'`);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// \u0000 - \u001f
|
|
433
|
+
it("should decode long escape sequences (lower cased)", () => {
|
|
434
|
+
for (let i = 0; i <= 0x1f; i++) {
|
|
435
|
+
const s = `"\\u${i.toString(16).padStart(4, "0").toLowerCase()}"`;
|
|
436
|
+
const actual = JSON.parse<string>(s);
|
|
437
|
+
const expected = String.fromCharCode(i);
|
|
438
|
+
expect(actual).toBe(expected, `Failed to decode ${s}`);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// \u0000 - \u001F
|
|
443
|
+
it("should decode long escape sequences (upper cased)", () => {
|
|
444
|
+
for (let i = 0; i <= 0x1f; i++) {
|
|
445
|
+
const s = `"\\u${i.toString(16).padStart(4, "0").toUpperCase()}"`;
|
|
446
|
+
const actual = JSON.parse<string>(s);
|
|
447
|
+
const expected = String.fromCharCode(i);
|
|
448
|
+
expect(actual).toBe(expected, `Failed to decode ${s}`);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// See https://datatracker.ietf.org/doc/html/rfc8259#section-7
|
|
453
|
+
it("should decode UTF-16 surrogate pairs", () => {
|
|
454
|
+
const s = '"\\uD834\\uDD1E"';
|
|
455
|
+
const actual = JSON.parse<string>(s);
|
|
456
|
+
const expected = "𝄞";
|
|
457
|
+
expect(actual).toBe(expected);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// Just because we can decode UTF-16 surrogate pairs, doesn't mean we should encode them.
|
|
461
|
+
it("should not encode UTF-16 surrogate pairs", () => {
|
|
462
|
+
const s = "𝄞";
|
|
463
|
+
const actual = JSON.stringify(s);
|
|
464
|
+
const expected = '"𝄞"';
|
|
465
|
+
expect(actual).toBe(expected);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("should encode multiple escape sequences", () => {
|
|
469
|
+
canSer('"""', '"\\"\\"\\""');
|
|
470
|
+
canSer('\\\\\\', '"\\\\\\\\\\\\"');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("cannot parse invalid escape sequences", () => {
|
|
474
|
+
expect(() => {
|
|
475
|
+
JSON.parse<string>('"\\z"');
|
|
476
|
+
}).toThrow();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("Ser/de special strings in object values", () => {
|
|
482
|
+
it("should serialize quotes in string in object", () => {
|
|
483
|
+
const o: ObjWithString = { s: '"""' };
|
|
484
|
+
const s = '{"s":"\\"\\"\\""}';
|
|
485
|
+
canSer(o, s);
|
|
486
|
+
});
|
|
487
|
+
it("should deserialize quotes in string in object", () => {
|
|
488
|
+
const o: ObjWithString = { s: '"""' };
|
|
489
|
+
const s = '{"s":"\\"\\"\\""}';
|
|
490
|
+
canDeser(s, o);
|
|
491
|
+
});
|
|
492
|
+
it("should serialize backslashes in string in object", () => {
|
|
493
|
+
const o: ObjWithString = { s: "\\\\\\" };
|
|
494
|
+
const s = '{"s":"\\\\\\\\\\\\"}';
|
|
495
|
+
canSer(o, s);
|
|
496
|
+
});
|
|
497
|
+
it("should deserialize backslashes in string in object", () => {
|
|
498
|
+
const o: ObjWithString = { s: "\\\\\\" };
|
|
499
|
+
const s = '{"s":"\\\\\\\\\\\\"}';
|
|
500
|
+
canDeser(s, o);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should deserialize slashes in string in object", () => {
|
|
504
|
+
const o: ObjWithString = { s: "//" };
|
|
505
|
+
const s = '{"s":"/\\/"}';
|
|
506
|
+
canDeser(s, o);
|
|
507
|
+
});
|
|
508
|
+
it("should deserialize slashes in string in array", () => {
|
|
509
|
+
const a = ["/", "/"];
|
|
510
|
+
const s = '["/","\/"]';
|
|
511
|
+
canDeser(s, a);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("should ser/de short escape sequences in strings in objects", () => {
|
|
515
|
+
const o: ObjWithString = { s: "\n\r\t\b\f" };
|
|
516
|
+
const s = '{"s":"\\n\\r\\t\\b\\f"}';
|
|
517
|
+
canSerde(o, s);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it("should ser/de short escape sequences in string arrays", () => {
|
|
521
|
+
const a = ["\n", "\r", "\t", "\b", "\f"];
|
|
522
|
+
const s = '["\\n","\\r","\\t","\\b","\\f"]';
|
|
523
|
+
canSerde(a, s);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it("should ser/de short escape sequences in string arrays in objects", () => {
|
|
527
|
+
const o: ObjectWithStringArray = { sa: ["\n", "\r", "\t", "\b", "\f"] };
|
|
528
|
+
const s = '{"sa":["\\n","\\r","\\t","\\b","\\f"]}';
|
|
529
|
+
canSerde(o, s);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("should ser/de long escape sequences in strings in objects", () => {
|
|
533
|
+
const singles = ["\n", "\r", "\t", "\b", "\f"];
|
|
534
|
+
let x = "";
|
|
535
|
+
let y = "";
|
|
536
|
+
for (let i = 0; i < 0x1F; i++) {
|
|
537
|
+
const c = String.fromCharCode(i);
|
|
538
|
+
if (singles.includes(c)) continue;
|
|
539
|
+
x += c;
|
|
540
|
+
y += `\\u${i.toString(16).padStart(4, "0")}`;
|
|
541
|
+
}
|
|
542
|
+
const o: ObjWithString = { s: x };
|
|
543
|
+
const s = `{"s":"${y}"}`;
|
|
544
|
+
canSerde(o, s);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("should ser/de long escape sequences in strings in arrays", () => {
|
|
548
|
+
const singles = ["\n", "\r", "\t", "\b", "\f"];
|
|
549
|
+
let x: string[] = [];
|
|
550
|
+
let y: string[] = [];
|
|
551
|
+
for (let i = 0; i < 0x1F; i++) {
|
|
552
|
+
const c = String.fromCharCode(i);
|
|
553
|
+
if (singles.includes(c)) continue;
|
|
554
|
+
x.push(c);
|
|
555
|
+
y.push(`\\u${i.toString(16).padStart(4, "0")}`);
|
|
556
|
+
}
|
|
557
|
+
const a = x;
|
|
558
|
+
const s = `["${y.join('","')}"]`;
|
|
559
|
+
canSerde(a, s);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("should ser/de long escape sequences in string arrays in objects", () => {
|
|
563
|
+
const singles = ["\n", "\r", "\t", "\b", "\f"];
|
|
564
|
+
let x: string[] = [];
|
|
565
|
+
let y: string[] = [];
|
|
566
|
+
for (let i = 0; i < 0x1F; i++) {
|
|
567
|
+
const c = String.fromCharCode(i);
|
|
568
|
+
if (singles.includes(c)) continue;
|
|
569
|
+
x.push(c);
|
|
570
|
+
y.push(`\\u${i.toString(16).padStart(4, "0")}`);
|
|
571
|
+
}
|
|
572
|
+
const o: ObjectWithStringArray = { sa: x };
|
|
573
|
+
const s = `{"sa":["${y.join('","')}"]}`;
|
|
574
|
+
canSerde(o, s);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe("Ser/de special strings in object keys", () => {
|
|
580
|
+
|
|
581
|
+
it("should ser/de escape sequences in key of object with int value", () => {
|
|
582
|
+
const o: ObjWithStrangeKey<i32> = { data: 123 };
|
|
583
|
+
const s = '{"a\\\\\\t\\"\\u0002b`c":123}';
|
|
584
|
+
canSerde(o, s);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("should ser/de escape sequences in key of object with float value", () => {
|
|
588
|
+
const o: ObjWithStrangeKey<f64> = { data: 123.4 };
|
|
589
|
+
const s = '{"a\\\\\\t\\"\\u0002b`c":123.4}';
|
|
590
|
+
canSerde(o, s);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("should ser/de escape sequences in key of object with string value", () => {
|
|
594
|
+
const o: ObjWithStrangeKey<string> = { data: "abc" };
|
|
595
|
+
const s = '{"a\\\\\\t\\"\\u0002b`c":"abc"}';
|
|
596
|
+
canSerde(o, s);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Something buggy in as-pect needs a dummy value reflected here
|
|
600
|
+
// or the subsequent test fails. It's not used in any test.
|
|
601
|
+
Reflect.toReflectedValue(0);
|
|
602
|
+
|
|
603
|
+
it("should ser/de escape sequences in map key", () => {
|
|
604
|
+
const m = new Map<string, string>();
|
|
605
|
+
m.set('a\\\t"\x02b', 'abc');
|
|
606
|
+
const s = '{"a\\\\\\t\\"\\u0002b":"abc"}';
|
|
607
|
+
canSerde(m, s);
|
|
608
|
+
});
|
|
609
|
+
it("should ser/de escape sequences in map value", () => {
|
|
610
|
+
const m = new Map<string, string>();
|
|
611
|
+
m.set('abc', 'a\\\t"\x02b');
|
|
612
|
+
const s = '{"abc":"a\\\\\\t\\"\\u0002b"}';
|
|
613
|
+
canSerde(m, s);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
@json
|
|
618
|
+
class ObjWithString {
|
|
619
|
+
s!: string;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
@json
|
|
623
|
+
class ObjectWithStringArray {
|
|
624
|
+
sa!: string[];
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
@json
|
|
628
|
+
class ObjectWithFloat {
|
|
629
|
+
f!: f64;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
@json
|
|
633
|
+
class ObjectWithFloatArray {
|
|
634
|
+
fa!: f64[];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@json
|
|
638
|
+
class ObjWithStrangeKey<T> {
|
|
639
|
+
@alias('a\\\t"\x02b`c')
|
|
640
|
+
data!: T;
|
|
641
|
+
}
|
package/assembly/src/chars.ts
CHANGED
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
@inline export const sCode = 115;
|
|
36
36
|
// @ts-ignore = Decorator is valid here
|
|
37
37
|
@inline export const nCode = 110;
|
|
38
|
+
// @ts-ignore = Decorator is valid here
|
|
39
|
+
@inline export const bCode = 98;
|
|
38
40
|
// Strings
|
|
39
41
|
// @ts-ignore: Decorator is valid here
|
|
40
42
|
@inline export const trueWord = "true";
|
|
@@ -58,6 +60,15 @@
|
|
|
58
60
|
@inline export const rightBracketWord = "]";
|
|
59
61
|
// @ts-ignore: Decorator is valid here
|
|
60
62
|
@inline export const quoteWord = "\"";
|
|
63
|
+
|
|
61
64
|
// Escape Codes
|
|
62
65
|
// @ts-ignore: Decorator is valid here
|
|
63
|
-
@inline export const
|
|
66
|
+
@inline export const backspaceCode = 8; // \b
|
|
67
|
+
// @ts-ignore: Decorator is valid here
|
|
68
|
+
@inline export const tabCode = 9; // \t
|
|
69
|
+
// @ts-ignore: Decorator is valid here
|
|
70
|
+
@inline export const newLineCode = 10; // \n
|
|
71
|
+
// @ts-ignore: Decorator is valid here
|
|
72
|
+
@inline export const formFeedCode = 12; // \f
|
|
73
|
+
// @ts-ignore: Decorator is valid here
|
|
74
|
+
@inline export const carriageReturnCode = 13; // \r
|
package/assembly/src/json.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { StringSink } from "as-string-sink/assembly";
|
|
|
2
2
|
import { isSpace } from "util/string";
|
|
3
3
|
import {
|
|
4
4
|
aCode,
|
|
5
|
+
bCode,
|
|
5
6
|
eCode,
|
|
6
7
|
fCode,
|
|
7
8
|
lCode,
|
|
@@ -14,20 +15,24 @@ import {
|
|
|
14
15
|
backSlashCode,
|
|
15
16
|
colonCode,
|
|
16
17
|
commaCode,
|
|
18
|
+
forwardSlashCode,
|
|
17
19
|
leftBraceCode,
|
|
18
20
|
leftBracketCode,
|
|
19
|
-
newLineCode,
|
|
20
21
|
quoteCode,
|
|
21
22
|
rightBraceCode,
|
|
22
23
|
rightBracketCode,
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
backspaceCode,
|
|
26
|
+
carriageReturnCode,
|
|
27
|
+
tabCode,
|
|
28
|
+
formFeedCode,
|
|
29
|
+
newLineCode,
|
|
30
|
+
|
|
25
31
|
commaWord,
|
|
26
32
|
quoteWord,
|
|
27
33
|
|
|
28
34
|
leftBraceWord,
|
|
29
35
|
leftBracketWord,
|
|
30
|
-
rightBraceWord,
|
|
31
36
|
rightBracketWord,
|
|
32
37
|
emptyArrayWord,
|
|
33
38
|
|
|
@@ -35,7 +40,7 @@ import {
|
|
|
35
40
|
falseWord,
|
|
36
41
|
nullWord,
|
|
37
42
|
} from "./chars";
|
|
38
|
-
import { snip_fast, unsafeCharCodeAt } from "./util";
|
|
43
|
+
import { snip_fast, unsafeCharCodeAt, containsCodePoint } from "./util";
|
|
39
44
|
import { Virtual } from "as-virtual/assembly";
|
|
40
45
|
|
|
41
46
|
/**
|
|
@@ -68,7 +73,7 @@ export namespace JSON {
|
|
|
68
73
|
// @ts-ignore: Hidden function
|
|
69
74
|
return data.__JSON_Serialize();
|
|
70
75
|
} else if (data instanceof Date) {
|
|
71
|
-
return "
|
|
76
|
+
return `"${data.toISOString()}"`;
|
|
72
77
|
} else if (isArrayLike<T>()) {
|
|
73
78
|
// @ts-ignore
|
|
74
79
|
if (data.length == 0) {
|
|
@@ -100,11 +105,11 @@ export namespace JSON {
|
|
|
100
105
|
for (let i = 0; i < data.length - 1; i++) {
|
|
101
106
|
// @ts-ignore
|
|
102
107
|
result.write(JSON.stringify(unchecked(data[i])));
|
|
103
|
-
result.
|
|
108
|
+
result.writeCodePoint(commaCode);
|
|
104
109
|
}
|
|
105
110
|
// @ts-ignore
|
|
106
111
|
result.write(JSON.stringify(unchecked(data[data.length - 1])));
|
|
107
|
-
result.
|
|
112
|
+
result.writeCodePoint(rightBracketCode);
|
|
108
113
|
return result.toString();
|
|
109
114
|
}
|
|
110
115
|
} else if (data instanceof Map) {
|
|
@@ -112,14 +117,14 @@ export namespace JSON {
|
|
|
112
117
|
let keys = data.keys();
|
|
113
118
|
let values = data.values();
|
|
114
119
|
for (let i = 0; i < data.size; i++) {
|
|
115
|
-
result.write(serializeString(keys[i].toString()));
|
|
116
|
-
result.
|
|
117
|
-
result.write(JSON.stringify(values[i]));
|
|
120
|
+
result.write(serializeString(unchecked(keys[i]).toString()));
|
|
121
|
+
result.writeCodePoint(colonCode);
|
|
122
|
+
result.write(JSON.stringify(unchecked(values[i])));
|
|
118
123
|
if (i < data.size - 1) {
|
|
119
|
-
result.
|
|
124
|
+
result.writeCodePoint(commaCode);
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
|
-
result.
|
|
127
|
+
result.writeCodePoint(rightBraceCode);
|
|
123
128
|
return result.toString();
|
|
124
129
|
} else {
|
|
125
130
|
throw new Error(
|
|
@@ -194,11 +199,11 @@ export namespace JSON {
|
|
|
194
199
|
for (let i = 0; i < data.length - 1; i++) {
|
|
195
200
|
// @ts-ignore
|
|
196
201
|
result.write(JSON.stringify(unchecked(data[i])));
|
|
197
|
-
result.
|
|
202
|
+
result.writeCodePoint(commaCode);
|
|
198
203
|
}
|
|
199
204
|
// @ts-ignore
|
|
200
205
|
result.write(JSON.stringify(unchecked(data[data.length - 1])));
|
|
201
|
-
result.
|
|
206
|
+
result.writeCodePoint(rightBracketCode);
|
|
202
207
|
out = result.toString();
|
|
203
208
|
return;
|
|
204
209
|
}
|
|
@@ -254,51 +259,8 @@ export namespace JSON {
|
|
|
254
259
|
@global @inline function __parseObjectValue<T>(data: string, initializeDefaultValues: boolean): T {
|
|
255
260
|
let type: T;
|
|
256
261
|
if (isString<T>()) {
|
|
257
|
-
let result = "";
|
|
258
|
-
let last = 0;
|
|
259
|
-
for (let i = 0; i < data.length; i++) {
|
|
260
|
-
// \\"
|
|
261
|
-
if (unsafeCharCodeAt(data, i) === backSlashCode) {
|
|
262
|
-
const char = unsafeCharCodeAt(data, ++i);
|
|
263
|
-
result += data.slice(last, i - 1);
|
|
264
|
-
if (char === 34) {
|
|
265
|
-
result += '"';
|
|
266
|
-
last = ++i;
|
|
267
|
-
} else if (char === 110) {
|
|
268
|
-
result += "\n";
|
|
269
|
-
last = ++i;
|
|
270
|
-
// 92 98 114 116 102 117
|
|
271
|
-
} else if (char >= 92 && char <= 117) {
|
|
272
|
-
if (char === 92) {
|
|
273
|
-
result += "\\";
|
|
274
|
-
last = ++i;
|
|
275
|
-
} else if (char === 98) {
|
|
276
|
-
result += "\b";
|
|
277
|
-
last = ++i;
|
|
278
|
-
} else if (char === 102) {
|
|
279
|
-
result += "\f";
|
|
280
|
-
last = ++i;
|
|
281
|
-
} else if (char === 114) {
|
|
282
|
-
result += "\r";
|
|
283
|
-
last = ++i;
|
|
284
|
-
} else if (char === 116) {
|
|
285
|
-
result += "\t";
|
|
286
|
-
last = ++i;
|
|
287
|
-
} else if (
|
|
288
|
-
char === 117 &&
|
|
289
|
-
load<u64>(changetype<usize>(data) + <usize>((i + 1) << 1)) ===
|
|
290
|
-
27584753879220272
|
|
291
|
-
) {
|
|
292
|
-
result += "\u000b";
|
|
293
|
-
i += 4;
|
|
294
|
-
last = ++i;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
result += data.slice(last);
|
|
300
262
|
// @ts-ignore
|
|
301
|
-
return
|
|
263
|
+
return data;
|
|
302
264
|
} else if (isBoolean<T>()) {
|
|
303
265
|
// @ts-ignore
|
|
304
266
|
return parseBoolean<T>(data);
|
|
@@ -327,118 +289,133 @@ export namespace JSON {
|
|
|
327
289
|
|
|
328
290
|
// @ts-ignore: Decorator
|
|
329
291
|
@inline function serializeString(data: string): string {
|
|
330
|
-
|
|
292
|
+
if (data.length === 0) {
|
|
293
|
+
return quoteWord + quoteWord;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let result = new StringSink(quoteWord);
|
|
331
297
|
|
|
332
298
|
let last: i32 = 0;
|
|
333
299
|
for (let i = 0; i < data.length; i++) {
|
|
334
300
|
const char = unsafeCharCodeAt(<string>data, i);
|
|
335
|
-
if (char ===
|
|
301
|
+
if (char === quoteCode || char === backSlashCode) {
|
|
336
302
|
result.write(<string>data, last, i);
|
|
337
303
|
result.writeCodePoint(backSlashCode);
|
|
338
304
|
last = i;
|
|
339
|
-
} else if (char
|
|
305
|
+
} else if (char < 16) {
|
|
340
306
|
result.write(<string>data, last, i);
|
|
341
307
|
last = i + 1;
|
|
342
308
|
switch (char) {
|
|
343
|
-
case
|
|
309
|
+
case backspaceCode: {
|
|
344
310
|
result.write("\\b");
|
|
345
311
|
break;
|
|
346
312
|
}
|
|
347
|
-
case
|
|
313
|
+
case tabCode: {
|
|
348
314
|
result.write("\\t");
|
|
349
315
|
break;
|
|
350
316
|
}
|
|
351
|
-
case
|
|
317
|
+
case newLineCode: {
|
|
352
318
|
result.write("\\n");
|
|
353
319
|
break;
|
|
354
320
|
}
|
|
355
|
-
case
|
|
356
|
-
result.write("\\x0B"); // \\u000b
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
case 12: {
|
|
321
|
+
case formFeedCode: {
|
|
360
322
|
result.write("\\f");
|
|
361
323
|
break;
|
|
362
324
|
}
|
|
363
|
-
case
|
|
325
|
+
case carriageReturnCode: {
|
|
364
326
|
result.write("\\r");
|
|
365
327
|
break;
|
|
366
328
|
}
|
|
329
|
+
default: {
|
|
330
|
+
// all chars 0-31 must be encoded as a four digit unicode escape sequence
|
|
331
|
+
// \u0000 to \u000f handled here
|
|
332
|
+
result.write("\\u000");
|
|
333
|
+
result.write(char.toString(16));
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
367
336
|
}
|
|
337
|
+
} else if (char < 32) {
|
|
338
|
+
result.write(<string>data, last, i);
|
|
339
|
+
last = i + 1;
|
|
340
|
+
// all chars 0-31 must be encoded as a four digit unicode escape sequence
|
|
341
|
+
// \u0010 to \u001f handled here
|
|
342
|
+
result.write("\\u00");
|
|
343
|
+
result.write(char.toString(16));
|
|
368
344
|
}
|
|
369
345
|
}
|
|
370
|
-
if (result.length === 1) {
|
|
371
|
-
return quoteWord + data + quoteWord;
|
|
372
|
-
}
|
|
373
346
|
result.write(<string>data, last);
|
|
374
|
-
result.
|
|
347
|
+
result.writeCodePoint(quoteCode);
|
|
375
348
|
return result.toString();
|
|
376
349
|
}
|
|
377
350
|
|
|
378
351
|
// @ts-ignore: Decorator
|
|
379
|
-
@inline function parseString(data: string): string {
|
|
380
|
-
|
|
381
|
-
let
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (unsafeCharCodeAt(data, i)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
352
|
+
@inline function parseString(data: string, start: i32 = 0, end: i32 = 0): string {
|
|
353
|
+
end = end || data.length - 1;
|
|
354
|
+
let result = StringSink.withCapacity(end - start - 1);
|
|
355
|
+
let last = start + 1;
|
|
356
|
+
for (let i = last; i < end; i++) {
|
|
357
|
+
if (unsafeCharCodeAt(data, i) !== backSlashCode) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const char = unsafeCharCodeAt(data, ++i);
|
|
361
|
+
result.write(data, last, i - 1);
|
|
362
|
+
switch (char) {
|
|
363
|
+
case quoteCode: {
|
|
388
364
|
result.writeCodePoint(quoteCode);
|
|
389
365
|
last = i + 1;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case backSlashCode: {
|
|
369
|
+
result.writeCodePoint(backSlashCode);
|
|
370
|
+
last = i + 1;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case forwardSlashCode: {
|
|
374
|
+
result.writeCodePoint(forwardSlashCode);
|
|
375
|
+
last = i + 1;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case bCode: {
|
|
379
|
+
result.writeCodePoint(backspaceCode);
|
|
380
|
+
last = i + 1;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case fCode: {
|
|
384
|
+
result.writeCodePoint(formFeedCode);
|
|
385
|
+
last = i + 1;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case nCode: {
|
|
389
|
+
result.writeCodePoint(newLineCode);
|
|
390
|
+
last = i + 1;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
case rCode: {
|
|
394
|
+
result.writeCodePoint(carriageReturnCode);
|
|
395
|
+
last = i + 1;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case tCode: {
|
|
399
|
+
result.writeCodePoint(tabCode);
|
|
400
|
+
last = i + 1;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case uCode: {
|
|
404
|
+
const code = u16.parse(data.slice(i + 1, i + 5), 16);
|
|
405
|
+
result.writeCodePoint(code);
|
|
406
|
+
i += 4;
|
|
407
|
+
last = i + 1;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
default: {
|
|
411
|
+
throw new Error(`JSON: Cannot parse "${data}" as string. Invalid escape sequence: \\${data.charAt(i)}`);
|
|
435
412
|
}
|
|
436
413
|
}
|
|
437
414
|
}
|
|
438
|
-
if (
|
|
439
|
-
result.write(data, last,
|
|
415
|
+
if (end > last) {
|
|
416
|
+
result.write(data, last, end);
|
|
440
417
|
}
|
|
441
|
-
return result.toString()
|
|
418
|
+
return result.toString()
|
|
442
419
|
}
|
|
443
420
|
|
|
444
421
|
// @ts-ignore: Decorator
|
|
@@ -491,7 +468,7 @@ export namespace JSON {
|
|
|
491
468
|
if (depth === 0) {
|
|
492
469
|
++arrayValueIndex;
|
|
493
470
|
// @ts-ignore
|
|
494
|
-
schema.__JSON_Set_Key
|
|
471
|
+
schema.__JSON_Set_Key(key, data, outerLoopIndex, arrayValueIndex, initializeDefaultValues);
|
|
495
472
|
outerLoopIndex = arrayValueIndex;
|
|
496
473
|
isKey = false;
|
|
497
474
|
break;
|
|
@@ -512,7 +489,7 @@ export namespace JSON {
|
|
|
512
489
|
if (depth === 0) {
|
|
513
490
|
++objectValueIndex;
|
|
514
491
|
// @ts-ignore
|
|
515
|
-
schema.__JSON_Set_Key
|
|
492
|
+
schema.__JSON_Set_Key(key, data, outerLoopIndex, objectValueIndex, initializeDefaultValues);
|
|
516
493
|
outerLoopIndex = objectValueIndex;
|
|
517
494
|
isKey = false;
|
|
518
495
|
break;
|
|
@@ -530,15 +507,19 @@ export namespace JSON {
|
|
|
530
507
|
if (char === backSlashCode && !escaping) {
|
|
531
508
|
escaping = true;
|
|
532
509
|
} else {
|
|
533
|
-
if (
|
|
534
|
-
char === quoteCode && !escaping
|
|
535
|
-
) {
|
|
510
|
+
if (char === quoteCode && !escaping) {
|
|
536
511
|
if (isKey === false) {
|
|
537
|
-
key
|
|
512
|
+
// perf: we can avoid creating a new string here if the key doesn't contain any escape sequences
|
|
513
|
+
if (containsCodePoint(data, backSlashCode, outerLoopIndex, stringValueIndex)) {
|
|
514
|
+
key.reinst(parseString(data, outerLoopIndex - 1, stringValueIndex));
|
|
515
|
+
} else {
|
|
516
|
+
key.reinst(data, outerLoopIndex, stringValueIndex);
|
|
517
|
+
}
|
|
538
518
|
isKey = true;
|
|
539
519
|
} else {
|
|
520
|
+
const value = parseString(data, outerLoopIndex - 1, stringValueIndex);
|
|
540
521
|
// @ts-ignore
|
|
541
|
-
schema.__JSON_Set_Key
|
|
522
|
+
schema.__JSON_Set_Key(key, value, 0, value.length, initializeDefaultValues);
|
|
542
523
|
isKey = false;
|
|
543
524
|
}
|
|
544
525
|
outerLoopIndex = ++stringValueIndex;
|
|
@@ -554,7 +535,7 @@ export namespace JSON {
|
|
|
554
535
|
unsafeCharCodeAt(data, ++outerLoopIndex) === lCode
|
|
555
536
|
) {
|
|
556
537
|
// @ts-ignore
|
|
557
|
-
schema.__JSON_Set_Key
|
|
538
|
+
schema.__JSON_Set_Key(key, nullWord, 0, 4, initializeDefaultValues);
|
|
558
539
|
isKey = false;
|
|
559
540
|
} else if (
|
|
560
541
|
char === tCode &&
|
|
@@ -563,7 +544,7 @@ export namespace JSON {
|
|
|
563
544
|
unsafeCharCodeAt(data, ++outerLoopIndex) === eCode
|
|
564
545
|
) {
|
|
565
546
|
// @ts-ignore
|
|
566
|
-
schema.__JSON_Set_Key
|
|
547
|
+
schema.__JSON_Set_Key(key, trueWord, 0, 4, initializeDefaultValues);
|
|
567
548
|
isKey = false;
|
|
568
549
|
} else if (
|
|
569
550
|
char === fCode &&
|
|
@@ -573,7 +554,7 @@ export namespace JSON {
|
|
|
573
554
|
unsafeCharCodeAt(data, ++outerLoopIndex) === eCode
|
|
574
555
|
) {
|
|
575
556
|
// @ts-ignore
|
|
576
|
-
schema.__JSON_Set_Key
|
|
557
|
+
schema.__JSON_Set_Key(key, falseWord, 0, 5, initializeDefaultValues);
|
|
577
558
|
isKey = false;
|
|
578
559
|
} else if ((char >= 48 && char <= 57) || char === 45) {
|
|
579
560
|
let numberValueIndex = ++outerLoopIndex;
|
|
@@ -581,7 +562,7 @@ export namespace JSON {
|
|
|
581
562
|
const char = unsafeCharCodeAt(data, numberValueIndex);
|
|
582
563
|
if (char === commaCode || char === rightBraceCode || isSpace(char)) {
|
|
583
564
|
// @ts-ignore
|
|
584
|
-
schema.__JSON_Set_Key
|
|
565
|
+
schema.__JSON_Set_Key(key, data, outerLoopIndex - 1, numberValueIndex, initializeDefaultValues);
|
|
585
566
|
outerLoopIndex = numberValueIndex;
|
|
586
567
|
isKey = false;
|
|
587
568
|
break;
|
|
@@ -664,11 +645,17 @@ export namespace JSON {
|
|
|
664
645
|
char === quoteCode && !escaping
|
|
665
646
|
) {
|
|
666
647
|
if (isKey === false) {
|
|
667
|
-
key
|
|
648
|
+
// perf: we can avoid creating a new string here if the key doesn't contain any escape sequences
|
|
649
|
+
if (containsCodePoint(data, backSlashCode, outerLoopIndex, stringValueIndex)) {
|
|
650
|
+
key.reinst(parseString(data, outerLoopIndex - 1, stringValueIndex));
|
|
651
|
+
} else {
|
|
652
|
+
key.reinst(data, outerLoopIndex, stringValueIndex);
|
|
653
|
+
}
|
|
668
654
|
isKey = true;
|
|
669
655
|
} else {
|
|
670
656
|
if (isString<valueof<T>>()) {
|
|
671
|
-
|
|
657
|
+
const value = parseString(data, outerLoopIndex - 1, stringValueIndex);
|
|
658
|
+
map.set(parseMapKey<indexof<T>>(key), value);
|
|
672
659
|
}
|
|
673
660
|
isKey = false;
|
|
674
661
|
}
|
|
@@ -788,7 +775,7 @@ export namespace JSON {
|
|
|
788
775
|
lastPos = i;
|
|
789
776
|
} else {
|
|
790
777
|
instr = false;
|
|
791
|
-
result.push(parseString(data
|
|
778
|
+
result.push(parseString(data, lastPos, i));
|
|
792
779
|
}
|
|
793
780
|
}
|
|
794
781
|
escaping = false;
|
|
@@ -803,16 +790,6 @@ export namespace JSON {
|
|
|
803
790
|
let lastPos = 1;
|
|
804
791
|
for (let i = 1; i < data.length - 1; i++) {
|
|
805
792
|
const char = unsafeCharCodeAt(data, i);
|
|
806
|
-
/*// if char == "t" && i+3 == "e"
|
|
807
|
-
if (char === tCode && data.charCodeAt(i + 3) === eCode) {
|
|
808
|
-
//i += 3;
|
|
809
|
-
result.push(parseBoolean<valueof<T>>(data.slice(lastPos, i+2)));
|
|
810
|
-
//i++;
|
|
811
|
-
} else if (char === fCode && data.charCodeAt(i + 4) === eCode) {
|
|
812
|
-
//i += 4;
|
|
813
|
-
result.push(parseBoolean<valueof<T>>(data.slice(lastPos, i+3)));
|
|
814
|
-
//i++;
|
|
815
|
-
}*/
|
|
816
793
|
if (char === tCode || char === fCode) {
|
|
817
794
|
lastPos = i;
|
|
818
795
|
} else if (char === eCode) {
|
|
@@ -830,7 +807,7 @@ export namespace JSON {
|
|
|
830
807
|
let i = 1;
|
|
831
808
|
for (; i < data.length - 1; i++) {
|
|
832
809
|
const char = unsafeCharCodeAt(data, i);
|
|
833
|
-
if (
|
|
810
|
+
if (lastPos === 0 && ((char >= 48 && char <= 57) || char === 45)) {
|
|
834
811
|
lastPos = i;
|
|
835
812
|
} else if ((isSpace(char) || char == commaCode) && lastPos > 0) {
|
|
836
813
|
result.push(parseNumber<valueof<T>>(data.slice(lastPos, i)));
|
package/assembly/src/util.ts
CHANGED
|
@@ -12,11 +12,11 @@ import { backSlashCode, quoteCode } from "./chars";
|
|
|
12
12
|
const result = new StringSink();
|
|
13
13
|
let instr = false;
|
|
14
14
|
for (let i = 0; i < data.length; i++) {
|
|
15
|
-
const char = data
|
|
15
|
+
const char = unsafeCharCodeAt(data, i);
|
|
16
16
|
if (instr === false && char === quoteCode) instr = true;
|
|
17
17
|
else if (
|
|
18
18
|
instr === true && char === quoteCode
|
|
19
|
-
&& data
|
|
19
|
+
&& unsafeCharCodeAt(data, i - 1) !== backSlashCode
|
|
20
20
|
) instr = false;
|
|
21
21
|
|
|
22
22
|
if (instr === false) {
|
|
@@ -347,4 +347,12 @@ import { backSlashCode, quoteCode } from "./chars";
|
|
|
347
347
|
return load<u16>(changetype<usize>(p1_data) + p1_start) == load<u16>(changetype<usize>(p2_data) + p2_start)
|
|
348
348
|
}
|
|
349
349
|
return memory.compare(changetype<usize>(p1_data) + p1_start, changetype<usize>(p2_data) + p2_start, p1_len) === 0;
|
|
350
|
-
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// @ts-ignore
|
|
353
|
+
@inline export function containsCodePoint(str: string, code: u32, start: i32, end: i32): bool {
|
|
354
|
+
for (let i = start; i <= end; i++) {
|
|
355
|
+
if (unsafeCharCodeAt(str, i) == code) return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
package/assembly/test.ts
CHANGED
|
@@ -55,7 +55,7 @@ class Wrapper<T> {
|
|
|
55
55
|
|
|
56
56
|
@serializable
|
|
57
57
|
class Foo {
|
|
58
|
-
@alias("
|
|
58
|
+
@alias("hello")
|
|
59
59
|
foo!: string;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -70,7 +70,7 @@ const foo: Wrapper<Foo> = {
|
|
|
70
70
|
|
|
71
71
|
foo.data.foo = "ha";
|
|
72
72
|
console.log(JSON.stringify(foo));
|
|
73
|
-
console.log(JSON.stringify(JSON.parse<Wrapper<Foo>>("{\"data\":{\"
|
|
73
|
+
console.log(JSON.stringify(JSON.parse<Wrapper<Foo>>("{\"data\":{\"hello\":\"ha\"}}")))
|
|
74
74
|
/*
|
|
75
75
|
// 9,325,755
|
|
76
76
|
bench("Stringify Object (Vec3)", () => {
|
package/package.json
CHANGED
package/transform/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Parser, Source, Tokenizer, } from "assemblyscript/dist/assemblyscript.js";
|
|
1
2
|
import { toString, isStdlib } from "visitor-as/dist/utils.js";
|
|
2
3
|
import { BaseVisitor, SimpleParser } from "visitor-as/dist/index.js";
|
|
3
4
|
import { Transform } from "assemblyscript/dist/transform.js";
|
|
@@ -17,7 +18,7 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
17
18
|
constructor() {
|
|
18
19
|
super(...arguments);
|
|
19
20
|
this.schemasList = [];
|
|
20
|
-
this.sources =
|
|
21
|
+
this.sources = new Set();
|
|
21
22
|
}
|
|
22
23
|
visitMethodDeclaration() { }
|
|
23
24
|
visitClassDeclaration(node) {
|
|
@@ -77,8 +78,10 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
77
78
|
let type = toString(member.type);
|
|
78
79
|
const name = member.name.text;
|
|
79
80
|
let aliasName = name;
|
|
81
|
+
// @ts-ignore
|
|
80
82
|
if (member.decorators && ((_d = member.decorators[0]) === null || _d === void 0 ? void 0 : _d.name.text) === "alias") {
|
|
81
83
|
if (member.decorators[0] && member.decorators[0].args[0]) {
|
|
84
|
+
// @ts-ignore
|
|
82
85
|
aliasName = member.decorators[0].args[0].value;
|
|
83
86
|
}
|
|
84
87
|
}
|
|
@@ -96,13 +99,12 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
96
99
|
"u64",
|
|
97
100
|
"i64",
|
|
98
101
|
].includes(type.toLowerCase())) {
|
|
99
|
-
this.currentClass.encodeStmts.push(
|
|
102
|
+
this.currentClass.encodeStmts.push(`${encodeKey(aliasName)}:\${this.${name}},`);
|
|
100
103
|
// @ts-ignore
|
|
101
|
-
this.currentClass.setDataStmts.push(`if (key.equals(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`);
|
|
104
|
+
this.currentClass.setDataStmts.push(`if (key.equals(${JSON.stringify(aliasName)})) {
|
|
105
|
+
this.${name} = __atoi_fast<${type}>(data, val_start << 1, val_end << 1);
|
|
106
|
+
return;
|
|
107
|
+
}`);
|
|
106
108
|
if (member.initializer) {
|
|
107
109
|
this.currentClass.initializeStmts.push(`this.${name} = ${toString(member.initializer)}`);
|
|
108
110
|
}
|
|
@@ -112,25 +114,23 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
112
114
|
"f32",
|
|
113
115
|
"f64",
|
|
114
116
|
].includes(type.toLowerCase())) {
|
|
115
|
-
this.currentClass.encodeStmts.push(
|
|
117
|
+
this.currentClass.encodeStmts.push(`${encodeKey(aliasName)}:\${this.${name}},`);
|
|
116
118
|
// @ts-ignore
|
|
117
|
-
this.currentClass.setDataStmts.push(`if (key.equals(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`);
|
|
119
|
+
this.currentClass.setDataStmts.push(`if (key.equals(${JSON.stringify(aliasName)})) {
|
|
120
|
+
this.${name} = __parseObjectValue<${type}>(data.slice(val_start, val_end), initializeDefaultValues);
|
|
121
|
+
return;
|
|
122
|
+
}`);
|
|
122
123
|
if (member.initializer) {
|
|
123
124
|
this.currentClass.initializeStmts.push(`this.${name} = ${toString(member.initializer)}`);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
else {
|
|
127
|
-
this.currentClass.encodeStmts.push(
|
|
128
|
+
this.currentClass.encodeStmts.push(`${encodeKey(aliasName)}:\${JSON.stringify<${type}>(this.${name})},`);
|
|
128
129
|
// @ts-ignore
|
|
129
|
-
this.currentClass.setDataStmts.push(`if (key.equals(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`);
|
|
130
|
+
this.currentClass.setDataStmts.push(`if (key.equals(${JSON.stringify(aliasName)})) {
|
|
131
|
+
this.${name} = __parseObjectValue<${type}>(val_start ? data.slice(val_start, val_end) : data, initializeDefaultValues);
|
|
132
|
+
return;
|
|
133
|
+
}`);
|
|
134
134
|
if (member.initializer) {
|
|
135
135
|
this.currentClass.initializeStmts.push(`this.${name} = ${toString(member.initializer)}`);
|
|
136
136
|
}
|
|
@@ -143,38 +143,32 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
143
143
|
this.currentClass.encodeStmts[this.currentClass.encodeStmts.length - 1] =
|
|
144
144
|
stmt.slice(0, stmt.length - 1);
|
|
145
145
|
serializeFunc = `
|
|
146
|
-
|
|
146
|
+
__JSON_Serialize(): string {
|
|
147
147
|
return \`{${this.currentClass.encodeStmts.join("")}}\`;
|
|
148
|
-
}
|
|
149
|
-
`;
|
|
148
|
+
}`;
|
|
150
149
|
}
|
|
151
150
|
else {
|
|
152
151
|
serializeFunc = `
|
|
153
|
-
|
|
152
|
+
__JSON_Serialize(): string {
|
|
154
153
|
return "{}";
|
|
155
|
-
}
|
|
156
|
-
`;
|
|
154
|
+
}`;
|
|
157
155
|
}
|
|
158
|
-
// Odd behavior here... When pairing this transform with asyncify, having @inline on __JSON_Set_Key<T> with a generic will cause it to freeze.
|
|
159
|
-
// Binaryen cannot predict and add/mangle code when it is genericed.
|
|
160
156
|
const setKeyFunc = `
|
|
161
|
-
__JSON_Set_Key
|
|
162
|
-
${
|
|
163
|
-
// @ts-ignore
|
|
164
|
-
this.currentClass.setDataStmts.join("")}
|
|
157
|
+
__JSON_Set_Key(key: __Virtual<string>, data: string, val_start: i32, val_end: i32, initializeDefaultValues: boolean): void {
|
|
158
|
+
${this.currentClass.setDataStmts.join("\n ")}
|
|
165
159
|
}
|
|
166
160
|
`;
|
|
167
161
|
let initializeFunc = "";
|
|
168
162
|
if (this.currentClass.initializeStmts.length > 0) {
|
|
169
163
|
initializeFunc = `
|
|
170
|
-
|
|
164
|
+
__JSON_Initialize(): void {
|
|
171
165
|
${this.currentClass.initializeStmts.join(";\n")};
|
|
172
166
|
}
|
|
173
167
|
`;
|
|
174
168
|
}
|
|
175
169
|
else {
|
|
176
170
|
initializeFunc = `
|
|
177
|
-
|
|
171
|
+
__JSON_Initialize(): void {}
|
|
178
172
|
`;
|
|
179
173
|
}
|
|
180
174
|
const serializeMethod = SimpleParser.parseClassMember(serializeFunc, node);
|
|
@@ -184,12 +178,37 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
184
178
|
const initializeMethod = SimpleParser.parseClassMember(initializeFunc, node);
|
|
185
179
|
node.members.push(initializeMethod);
|
|
186
180
|
this.schemasList.push(this.currentClass);
|
|
187
|
-
|
|
181
|
+
this.sources.add(node.name.range.source);
|
|
182
|
+
// Uncomment to see the generated code for debugging.
|
|
183
|
+
// console.log(serializeFunc);
|
|
184
|
+
// console.log(setKeyFunc);
|
|
185
|
+
// console.log(initializeFunc);
|
|
188
186
|
}
|
|
189
187
|
visitSource(node) {
|
|
190
188
|
super.visitSource(node);
|
|
189
|
+
// Only add the import statement to sources that have JSON decorated classes.
|
|
190
|
+
if (!this.sources.has(node)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Note, the following one liner would be easier, but it fails with an assertion error
|
|
194
|
+
// because as-virtual's SimpleParser doesn't set the parser.currentSource correctly.
|
|
195
|
+
//
|
|
196
|
+
// const stmt = SimpleParser.parseTopLevelStatement('import { Virtual as __Virtual } from "as-virtual/assembly";');
|
|
197
|
+
// ... So we have to do it the long way:
|
|
198
|
+
const s = 'import { Virtual as __Virtual } from "as-virtual/assembly";';
|
|
199
|
+
const t = new Tokenizer(new Source(0 /* SourceKind.User */, "index.ts", s));
|
|
200
|
+
const p = new Parser();
|
|
201
|
+
p.currentSource = t.source;
|
|
202
|
+
const stmt = p.parseTopLevelStatement(t);
|
|
203
|
+
// Add the import statement to the top of the source.
|
|
204
|
+
node.statements.unshift(stmt);
|
|
191
205
|
}
|
|
192
206
|
}
|
|
207
|
+
function encodeKey(aliasName) {
|
|
208
|
+
return JSON.stringify(aliasName)
|
|
209
|
+
.replace(/\\/g, "\\\\")
|
|
210
|
+
.replace(/\`/g, '\\`');
|
|
211
|
+
}
|
|
193
212
|
export default class Transformer extends Transform {
|
|
194
213
|
// Trigger the transform after parse.
|
|
195
214
|
afterParse(parser) {
|
package/transform/package.json
CHANGED
package/transform/src/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ClassDeclaration,
|
|
3
3
|
FieldDeclaration,
|
|
4
|
-
Source,
|
|
5
4
|
Parser,
|
|
6
|
-
|
|
5
|
+
Source,
|
|
6
|
+
SourceKind,
|
|
7
|
+
Tokenizer,
|
|
8
|
+
} from "assemblyscript/dist/assemblyscript.js";
|
|
9
|
+
|
|
7
10
|
import { toString, isStdlib } from "visitor-as/dist/utils.js";
|
|
8
11
|
import { BaseVisitor, SimpleParser } from "visitor-as/dist/index.js";
|
|
9
12
|
import { Transform } from "assemblyscript/dist/transform.js";
|
|
@@ -23,7 +26,7 @@ class SchemaData {
|
|
|
23
26
|
class AsJSONTransform extends BaseVisitor {
|
|
24
27
|
public schemasList: SchemaData[] = [];
|
|
25
28
|
public currentClass!: SchemaData;
|
|
26
|
-
public sources
|
|
29
|
+
public sources = new Set<Source>();
|
|
27
30
|
|
|
28
31
|
visitMethodDeclaration(): void { }
|
|
29
32
|
visitClassDeclaration(node: ClassDeclaration): void {
|
|
@@ -89,8 +92,11 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
89
92
|
|
|
90
93
|
const name = member.name.text;
|
|
91
94
|
let aliasName = name;
|
|
95
|
+
|
|
96
|
+
// @ts-ignore
|
|
92
97
|
if (member.decorators && member.decorators[0]?.name.text === "alias") {
|
|
93
98
|
if (member.decorators[0] && member.decorators[0].args![0]) {
|
|
99
|
+
// @ts-ignore
|
|
94
100
|
aliasName = member.decorators[0].args![0].value;
|
|
95
101
|
}
|
|
96
102
|
}
|
|
@@ -111,15 +117,14 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
111
117
|
].includes(type.toLowerCase())
|
|
112
118
|
) {
|
|
113
119
|
this.currentClass.encodeStmts.push(
|
|
114
|
-
|
|
120
|
+
`${encodeKey(aliasName)}:\${this.${name}},`
|
|
115
121
|
);
|
|
116
122
|
// @ts-ignore
|
|
117
123
|
this.currentClass.setDataStmts.push(
|
|
118
|
-
`if (key.equals(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`
|
|
124
|
+
`if (key.equals(${JSON.stringify(aliasName)})) {
|
|
125
|
+
this.${name} = __atoi_fast<${type}>(data, val_start << 1, val_end << 1);
|
|
126
|
+
return;
|
|
127
|
+
}`
|
|
123
128
|
);
|
|
124
129
|
if (member.initializer) {
|
|
125
130
|
this.currentClass.initializeStmts.push(
|
|
@@ -134,15 +139,14 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
134
139
|
].includes(type.toLowerCase())
|
|
135
140
|
) {
|
|
136
141
|
this.currentClass.encodeStmts.push(
|
|
137
|
-
|
|
142
|
+
`${encodeKey(aliasName)}:\${this.${name}},`
|
|
138
143
|
);
|
|
139
144
|
// @ts-ignore
|
|
140
145
|
this.currentClass.setDataStmts.push(
|
|
141
|
-
`if (key.equals(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
`
|
|
146
|
+
`if (key.equals(${JSON.stringify(aliasName)})) {
|
|
147
|
+
this.${name} = __parseObjectValue<${type}>(data.slice(val_start, val_end), initializeDefaultValues);
|
|
148
|
+
return;
|
|
149
|
+
}`
|
|
146
150
|
);
|
|
147
151
|
if (member.initializer) {
|
|
148
152
|
this.currentClass.initializeStmts.push(
|
|
@@ -151,15 +155,14 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
151
155
|
}
|
|
152
156
|
} else {
|
|
153
157
|
this.currentClass.encodeStmts.push(
|
|
154
|
-
|
|
158
|
+
`${encodeKey(aliasName)}:\${JSON.stringify<${type}>(this.${name})},`
|
|
155
159
|
);
|
|
156
160
|
// @ts-ignore
|
|
157
161
|
this.currentClass.setDataStmts.push(
|
|
158
|
-
`if (key.equals(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`
|
|
162
|
+
`if (key.equals(${JSON.stringify(aliasName)})) {
|
|
163
|
+
this.${name} = __parseObjectValue<${type}>(val_start ? data.slice(val_start, val_end) : data, initializeDefaultValues);
|
|
164
|
+
return;
|
|
165
|
+
}`
|
|
163
166
|
);
|
|
164
167
|
if (member.initializer) {
|
|
165
168
|
this.currentClass.initializeStmts.push(
|
|
@@ -180,26 +183,19 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
180
183
|
this.currentClass.encodeStmts[this.currentClass.encodeStmts.length - 1] =
|
|
181
184
|
stmt!.slice(0, stmt.length - 1);
|
|
182
185
|
serializeFunc = `
|
|
183
|
-
|
|
186
|
+
__JSON_Serialize(): string {
|
|
184
187
|
return \`{${this.currentClass.encodeStmts.join("")}}\`;
|
|
185
|
-
}
|
|
186
|
-
`;
|
|
188
|
+
}`;
|
|
187
189
|
} else {
|
|
188
190
|
serializeFunc = `
|
|
189
|
-
|
|
191
|
+
__JSON_Serialize(): string {
|
|
190
192
|
return "{}";
|
|
191
|
-
}
|
|
192
|
-
`;
|
|
193
|
+
}`;
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
// Odd behavior here... When pairing this transform with asyncify, having @inline on __JSON_Set_Key<T> with a generic will cause it to freeze.
|
|
196
|
-
// Binaryen cannot predict and add/mangle code when it is genericed.
|
|
197
196
|
const setKeyFunc = `
|
|
198
|
-
__JSON_Set_Key
|
|
199
|
-
${
|
|
200
|
-
// @ts-ignore
|
|
201
|
-
this.currentClass.setDataStmts.join("")
|
|
202
|
-
}
|
|
197
|
+
__JSON_Set_Key(key: __Virtual<string>, data: string, val_start: i32, val_end: i32, initializeDefaultValues: boolean): void {
|
|
198
|
+
${this.currentClass.setDataStmts.join("\n ")}
|
|
203
199
|
}
|
|
204
200
|
`;
|
|
205
201
|
|
|
@@ -207,13 +203,13 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
207
203
|
|
|
208
204
|
if (this.currentClass.initializeStmts.length > 0) {
|
|
209
205
|
initializeFunc = `
|
|
210
|
-
|
|
206
|
+
__JSON_Initialize(): void {
|
|
211
207
|
${this.currentClass.initializeStmts.join(";\n")};
|
|
212
208
|
}
|
|
213
209
|
`;
|
|
214
210
|
} else {
|
|
215
211
|
initializeFunc = `
|
|
216
|
-
|
|
212
|
+
__JSON_Initialize(): void {}
|
|
217
213
|
`;
|
|
218
214
|
}
|
|
219
215
|
const serializeMethod = SimpleParser.parseClassMember(serializeFunc, node);
|
|
@@ -226,13 +222,45 @@ class AsJSONTransform extends BaseVisitor {
|
|
|
226
222
|
node.members.push(initializeMethod);
|
|
227
223
|
|
|
228
224
|
this.schemasList.push(this.currentClass);
|
|
229
|
-
|
|
225
|
+
this.sources.add(node.name.range.source);
|
|
226
|
+
|
|
227
|
+
// Uncomment to see the generated code for debugging.
|
|
228
|
+
// console.log(serializeFunc);
|
|
229
|
+
// console.log(setKeyFunc);
|
|
230
|
+
// console.log(initializeFunc);
|
|
230
231
|
}
|
|
232
|
+
|
|
231
233
|
visitSource(node: Source): void {
|
|
232
234
|
super.visitSource(node);
|
|
235
|
+
|
|
236
|
+
// Only add the import statement to sources that have JSON decorated classes.
|
|
237
|
+
if (!this.sources.has(node)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Note, the following one liner would be easier, but it fails with an assertion error
|
|
242
|
+
// because as-virtual's SimpleParser doesn't set the parser.currentSource correctly.
|
|
243
|
+
//
|
|
244
|
+
// const stmt = SimpleParser.parseTopLevelStatement('import { Virtual as __Virtual } from "as-virtual/assembly";');
|
|
245
|
+
|
|
246
|
+
// ... So we have to do it the long way:
|
|
247
|
+
const s = 'import { Virtual as __Virtual } from "as-virtual/assembly";'
|
|
248
|
+
const t = new Tokenizer(new Source(SourceKind.User, "index.ts", s));
|
|
249
|
+
const p = new Parser();
|
|
250
|
+
p.currentSource = t.source;
|
|
251
|
+
const stmt = p.parseTopLevelStatement(t)!;
|
|
252
|
+
|
|
253
|
+
// Add the import statement to the top of the source.
|
|
254
|
+
node.statements.unshift(stmt);
|
|
233
255
|
}
|
|
234
256
|
}
|
|
235
257
|
|
|
258
|
+
function encodeKey(aliasName: string): string {
|
|
259
|
+
return JSON.stringify(aliasName)
|
|
260
|
+
.replace(/\\/g, "\\\\")
|
|
261
|
+
.replace(/\`/g, '\\`');
|
|
262
|
+
}
|
|
263
|
+
|
|
236
264
|
export default class Transformer extends Transform {
|
|
237
265
|
// Trigger the transform after parse.
|
|
238
266
|
afterParse(parser: Parser): void {
|