libshv-js 3.0.0

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/src/cpon.ts ADDED
@@ -0,0 +1,920 @@
1
+ import {type RpcValue, type RpcValueType, type DateTime, type List, Decimal, Double, IMap, Int, MetaMap, RpcValueWithMetaData, ShvMap, UInt, withOffset} from './rpcvalue.ts';
2
+ import {PackContext, UnpackContext} from './cpcontext.ts';
3
+
4
+ const hexify = (byte: number) => {
5
+ if (byte < 10) {
6
+ return 48 + byte;
7
+ }
8
+
9
+ if (byte < 16) {
10
+ return 97 + byte - 10;
11
+ }
12
+ return '?'.codePointAt(0)!;
13
+ };
14
+
15
+ const unhex = (byte: number) => {
16
+ if (byte >= 48 && byte <= 57) { // 0-9
17
+ return byte - 48;
18
+ }
19
+ if (byte >= 65 && byte <= 70) { // A-F
20
+ return byte - 65 + 10;
21
+ }
22
+ if (byte >= 97 && byte <= 102) { // a-f
23
+ return byte - 97 + 10;
24
+ }
25
+ throw new TypeError('Invalid HEX digit: ' + byte);
26
+ };
27
+
28
+ const utf8ToString = (bytearray: ArrayBuffer) => {
29
+ const uint8_array = new Uint8Array(bytearray);
30
+ let str = '';
31
+ for (let i = 0; i < uint8_array.length; i++) {
32
+ const value = uint8_array[i];
33
+
34
+ if (value < 0x80) {
35
+ str += String.fromCodePoint(value);
36
+ } else if (value > 0xBF && value < 0xE0) {
37
+ str += String.fromCodePoint(((value & 0x1F) << 6) | (uint8_array[i + 1] & 0x3F));
38
+ i += 1;
39
+ } else if (value > 0xDF && value < 0xF0) {
40
+ str += String.fromCodePoint(((value & 0x0F) << 12) | ((uint8_array[i + 1] & 0x3F) << 6) | (uint8_array[i + 2] & 0x3F));
41
+ i += 2;
42
+ } else {
43
+ // surrogate pair
44
+ const char_code = (((value & 0x07) << 18) | ((uint8_array[i + 1] & 0x3F) << 12) | ((uint8_array[i + 2] & 0x3F) << 6) | (uint8_array[i + 3] & 0x3F)) - 0x01_00_00;
45
+
46
+ str += String.fromCodePoint((char_code >> 10) | 0xD8_00, (char_code & 0x03_FF) | 0xDC_00);
47
+ i += 3;
48
+ }
49
+ }
50
+ return str;
51
+ };
52
+
53
+ class CponReader {
54
+ ctx: UnpackContext;
55
+ constructor(unpack_context: UnpackContext) {
56
+ this.ctx = unpack_context;
57
+ }
58
+
59
+ read(): RpcValue {
60
+ let meta: MetaMap | undefined;
61
+ this.skipWhitespace();
62
+ let b = this.ctx.peekByte();
63
+ if (b === '<'.codePointAt(0)) {
64
+ meta = this.readMetaMap();
65
+ }
66
+
67
+ const impl_return = (x: RpcValueType) => {
68
+ const ret = meta !== undefined ? new RpcValueWithMetaData(x, meta) : x;
69
+ return ret;
70
+ };
71
+
72
+ this.skipWhitespace();
73
+ b = this.ctx.peekByte();
74
+ // console.log("CHAR:", b, String.fromCodePoint(b));
75
+ // [0-9+-]
76
+ if ((b >= 48 && b <= 57) || b === 43 || b === 45) {
77
+ return impl_return(this.readNumber());
78
+ }
79
+
80
+ if (b === '"'.codePointAt(0)) {
81
+ return impl_return(this.readCString());
82
+ }
83
+
84
+ if (b === 'b'.codePointAt(0)) {
85
+ this.ctx.getByte();
86
+ b = this.ctx.peekByte();
87
+ if (b === '"'.codePointAt(0)) {
88
+ return impl_return(this.readBlobEsc());
89
+ }
90
+ throw new TypeError('Invalid Blob prefix.');
91
+ }
92
+
93
+ if (b === 'x'.codePointAt(0)) {
94
+ this.ctx.getByte();
95
+ b = this.ctx.peekByte();
96
+ if (b === '"'.codePointAt(0)) {
97
+ return impl_return(this.readBlobHex());
98
+ }
99
+ throw new TypeError('Invalid HEX Blob prefix.');
100
+ }
101
+
102
+ if (b === '['.codePointAt(0)) {
103
+ return impl_return(this.readList());
104
+ }
105
+
106
+ if (b === '{'.codePointAt(0)) {
107
+ return impl_return(this.readMap());
108
+ }
109
+
110
+ if (b === 'i'.codePointAt(0)) {
111
+ this.ctx.getByte();
112
+ b = this.ctx.peekByte();
113
+ if (b === '{'.codePointAt(0)) {
114
+ return impl_return(this.readIMap());
115
+ }
116
+ throw new TypeError('Invalid IMap prefix.');
117
+ }
118
+
119
+ if (b === 'd'.codePointAt(0)) {
120
+ this.ctx.getByte();
121
+ b = this.ctx.peekByte();
122
+ if (b === '"'.codePointAt(0)) {
123
+ return impl_return(this.readDateTime());
124
+ }
125
+ throw new TypeError('Invalid DateTime prefix.');
126
+ }
127
+
128
+ if (b === 't'.codePointAt(0)) {
129
+ this.ctx.getBytes('true');
130
+ return impl_return(true);
131
+ }
132
+
133
+ if (b === 'f'.codePointAt(0)) {
134
+ this.ctx.getBytes('false');
135
+ return impl_return(false);
136
+ }
137
+
138
+ if (b === 'n'.codePointAt(0)) {
139
+ this.ctx.getBytes('null');
140
+ return impl_return(undefined);
141
+ }
142
+
143
+ throw new TypeError('Malformed Cpon input.');
144
+ }
145
+
146
+ skipWhitespace() {
147
+ const SPACE = ' '.codePointAt(0)!;
148
+ const SLASH = '/'.codePointAt(0)!;
149
+ const STAR = '*'.codePointAt(0)!;
150
+ const LF = '\n'.codePointAt(0)!;
151
+ const KEY_DELIM = ':'.codePointAt(0)!;
152
+ const FIELD_DELIM = ','.codePointAt(0)!;
153
+ while (true) {
154
+ let b = this.ctx.peekByte();
155
+ if (b < 1) {
156
+ return;
157
+ }
158
+
159
+ if (b > SPACE) {
160
+ switch (b) {
161
+ case SLASH: {
162
+ this.ctx.getByte();
163
+ b = this.ctx.getByte();
164
+ if (b === STAR) {
165
+ // multiline_comment_entered;
166
+ while (true) {
167
+ b = this.ctx.getByte();
168
+ if (b === STAR) {
169
+ b = this.ctx.getByte();
170
+ if (b === SLASH) {
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ } else if (b === SLASH) {
176
+ // to end of line comment entered;
177
+ while (true) {
178
+ b = this.ctx.getByte();
179
+ if (b === LF) {
180
+ break;
181
+ }
182
+ }
183
+ } else {
184
+ throw new TypeError('Malformed comment');
185
+ }
186
+ break;
187
+ }
188
+ case KEY_DELIM: {
189
+ this.ctx.getByte();
190
+ continue;
191
+ }
192
+ case FIELD_DELIM: {
193
+ this.ctx.getByte();
194
+ continue;
195
+ }
196
+ default:
197
+ return;
198
+ }
199
+ } else {
200
+ this.ctx.getByte();
201
+ }
202
+ }
203
+ }
204
+
205
+ readDateTime() {
206
+ let year = 0;
207
+ let month = 0;
208
+ let day = 1;
209
+ let hour = 0;
210
+ let min = 0;
211
+ let sec = 0;
212
+ let msec = 0;
213
+ let utc_offset = 0;
214
+
215
+ this.ctx.getByte(); // eat '"'
216
+ let b = this.ctx.peekByte();
217
+ if (b === '"'.codePointAt(0)) {
218
+ // d"" invalid data time
219
+ this.ctx.getByte();
220
+ throw new TypeError('Malformed empty date separator in DateTime');
221
+ }
222
+
223
+ year = Number(this.readInt());
224
+
225
+ b = this.ctx.getByte();
226
+ if (b !== '-'.codePointAt(0)) {
227
+ throw new TypeError('Malformed year-month separator in DateTime');
228
+ }
229
+ month = Number(this.readInt());
230
+
231
+ b = this.ctx.getByte();
232
+ if (b !== '-'.codePointAt(0)) {
233
+ throw new TypeError('Malformed year-month separator in DateTime');
234
+ }
235
+ day = Number(this.readInt());
236
+
237
+ b = this.ctx.getByte();
238
+ if (b !== ' '.codePointAt(0) && b !== 'T'.codePointAt(0)) {
239
+ throw new TypeError('Malformed date-time separator in DateTime');
240
+ }
241
+ hour = Number(this.readInt());
242
+
243
+ b = this.ctx.getByte();
244
+ if (b !== ':'.codePointAt(0)) {
245
+ throw new TypeError('Malformed year-month separator in DateTime');
246
+ }
247
+ min = Number(this.readInt());
248
+
249
+ b = this.ctx.getByte();
250
+ if (b !== ':'.codePointAt(0)) {
251
+ throw new TypeError('Malformed year-month separator in DateTime');
252
+ }
253
+ sec = Number(this.readInt());
254
+
255
+ b = this.ctx.peekByte();
256
+ if (b === '.'.codePointAt(0)) {
257
+ this.ctx.getByte();
258
+ msec = Number(this.readInt());
259
+ }
260
+
261
+ b = this.ctx.peekByte();
262
+ if (b === 'Z'.codePointAt(0)) {
263
+ // zulu time
264
+ this.ctx.getByte();
265
+ } else if (b === '+'.codePointAt(0) || b === '-'.codePointAt(0)) {
266
+ // UTC time offset
267
+ this.ctx.getByte();
268
+ const ix1 = this.ctx.index;
269
+ const val = Number(this.readInt());
270
+ const n = this.ctx.index - ix1;
271
+ if (!(n === 2 || n === 4)) {
272
+ throw new TypeError('Malformed TS offset in DateTime.');
273
+ }
274
+ if (n === 2) {
275
+ utc_offset = 60 * val;
276
+ } else if (n === 4) {
277
+ utc_offset = (60 * (Math.trunc(val / 100))) + (val % 100);
278
+ }
279
+ if (b === '-'.codePointAt(0)) {
280
+ utc_offset = -utc_offset;
281
+ }
282
+ }
283
+
284
+ b = this.ctx.getByte();
285
+ if (b !== '"'.codePointAt(0)) {
286
+ throw new TypeError('DateTime literal should be terminated by \'"\'.');
287
+ }
288
+
289
+ // let epoch_sec = CponReader.timegm(year, month, mday, hour, min, sec);
290
+ let epoch_msec = Date.UTC(year, month - 1, day, hour, min, sec, msec);
291
+ epoch_msec -= utc_offset * 60_000;
292
+ return withOffset(new Date(epoch_msec), utc_offset);
293
+ }
294
+
295
+ readCString() {
296
+ const pctx = new PackContext();
297
+ this.ctx.getByte(); // eat '"'
298
+ while (true) {
299
+ let b = this.ctx.getByte();
300
+ if (b === '\\'.codePointAt(0)) {
301
+ b = this.ctx.getByte();
302
+ switch (b) {
303
+ case '\\'.codePointAt(0):
304
+ pctx.putByte('\\'.codePointAt(0)!);
305
+ break;
306
+ case '"'.codePointAt(0):
307
+ pctx.putByte('"'.codePointAt(0)!);
308
+ break;
309
+ case 'b'.codePointAt(0):
310
+ pctx.putByte('\b'.codePointAt(0)!);
311
+ break;
312
+ case 'f'.codePointAt(0):
313
+ pctx.putByte('\f'.codePointAt(0)!);
314
+ break;
315
+ case 'n'.codePointAt(0):
316
+ pctx.putByte('\n'.codePointAt(0)!);
317
+ break;
318
+ case 'r'.codePointAt(0):
319
+ pctx.putByte('\r'.codePointAt(0)!);
320
+ break;
321
+ case 't'.codePointAt(0):
322
+ pctx.putByte('\t'.codePointAt(0)!);
323
+ break;
324
+ case '0'.codePointAt(0):
325
+ pctx.putByte(0);
326
+ break;
327
+ default:
328
+ pctx.putByte(b);
329
+ break;
330
+ }
331
+ } else if (b === '"'.codePointAt(0)) {
332
+ // end of string
333
+ break;
334
+ } else {
335
+ pctx.putByte(b);
336
+ }
337
+ }
338
+ return utf8ToString(pctx.buffer());
339
+ }
340
+
341
+ readBlobEsc() {
342
+ const pctx = new PackContext();
343
+ this.ctx.getByte(); // eat '"'
344
+ while (true) {
345
+ let b = this.ctx.getByte();
346
+ if (b === '\\'.codePointAt(0)) {
347
+ b = this.ctx.getByte();
348
+ switch (b) {
349
+ case '\\'.codePointAt(0):
350
+ pctx.putByte('\\'.codePointAt(0)!);
351
+ break;
352
+ case '"'.codePointAt(0):
353
+ pctx.putByte('"'.codePointAt(0)!);
354
+ break;
355
+ case 'n'.codePointAt(0):
356
+ pctx.putByte('\n'.codePointAt(0)!);
357
+ break;
358
+ case 'r'.codePointAt(0):
359
+ pctx.putByte('\r'.codePointAt(0)!);
360
+ break;
361
+ case 't'.codePointAt(0):
362
+ pctx.putByte('\t'.codePointAt(0)!);
363
+ break;
364
+ default: {
365
+ const b2 = (unhex(b) * 16) + unhex(this.ctx.getByte());
366
+ pctx.putByte(b2);
367
+ break;
368
+ }
369
+ }
370
+ } else if (b === '"'.codePointAt(0)) {
371
+ // end of string
372
+ break;
373
+ } else if (b < 128) {
374
+ pctx.putByte(b);
375
+ } else {
376
+ throw new TypeError('Escaped Blob characters must be lower than 128, code: ' + b);
377
+ }
378
+ }
379
+ return pctx.buffer();
380
+ }
381
+
382
+ readBlobHex() {
383
+ const pctx = new PackContext();
384
+ this.ctx.getByte(); // eat '"'
385
+ while (true) {
386
+ const b = this.ctx.getByte();
387
+ if (b === '"'.codePointAt(0)) {
388
+ // end of string
389
+ break;
390
+ } else {
391
+ const b2 = (unhex(b) * 16) + unhex(this.ctx.getByte());
392
+ pctx.putByte(b2);
393
+ }
394
+ }
395
+ return pctx.buffer();
396
+ }
397
+
398
+ readList() {
399
+ const lst = [];
400
+ this.ctx.getByte(); // eat '['
401
+ while (true) {
402
+ this.skipWhitespace();
403
+ const b = this.ctx.peekByte();
404
+ if (b === ']'.codePointAt(0)) {
405
+ this.ctx.getByte();
406
+ break;
407
+ }
408
+ const item = this.read();
409
+ lst.push(item);
410
+ }
411
+ return lst;
412
+ }
413
+
414
+ readMetaMap() {
415
+ return this.implReadMap(MetaMap, '>'.codePointAt(0)!);
416
+ }
417
+
418
+ readMap() {
419
+ return this.implReadMap(ShvMap, '}'.codePointAt(0)!);
420
+ }
421
+
422
+ readIMap() {
423
+ return this.implReadMap(IMap, '}'.codePointAt(0)!);
424
+ }
425
+
426
+ readInt() {
427
+ let base = 10;
428
+ let val = 0;
429
+ let neg = 0;
430
+ let n = 0;
431
+ for (; ; n++) {
432
+ const b = this.ctx.peekByte();
433
+ if (b < 0) {
434
+ break;
435
+ }
436
+ if (b === 43 || b === 45) { // '+','-'
437
+ if (n !== 0) {
438
+ break;
439
+ }
440
+ this.ctx.getByte();
441
+ if (b === 45) {
442
+ neg = 1;
443
+ }
444
+ } else if (b === 120) { // 'x'
445
+ if (n === 1 && val !== 0) {
446
+ break;
447
+ }
448
+ if (n !== 1) {
449
+ break;
450
+ }
451
+ this.ctx.getByte();
452
+ base = 16;
453
+ } else if (b >= 48 && b <= 57) { // '0' - '9'
454
+ this.ctx.getByte();
455
+ val *= base;
456
+ val += b - 48;
457
+ } else if (b >= 65 && b <= 70) { // 'A' - 'F'
458
+ if (base !== 16) {
459
+ break;
460
+ }
461
+ this.ctx.getByte();
462
+ val *= base;
463
+ val += b - 65 + 10;
464
+ } else if (b >= 97 && b <= 102) { // 'a' - 'f'
465
+ if (base !== 16) {
466
+ break;
467
+ }
468
+ this.ctx.getByte();
469
+ val *= base;
470
+ val += b - 97 + 10;
471
+ } else {
472
+ break;
473
+ }
474
+ }
475
+ if (neg) {
476
+ val = -val;
477
+ }
478
+ return new Int(val);
479
+ }
480
+
481
+ readNumber() {
482
+ let mantisa = 0;
483
+ let exponent = 0;
484
+ let decimals = 0;
485
+ let dec_cnt = 0;
486
+ let is_decimal = false;
487
+ let is_uint = false;
488
+ let is_neg = false;
489
+
490
+ let b = this.ctx.peekByte();
491
+ if (b === 43) {// '+'
492
+ is_neg = false;
493
+ b = this.ctx.getByte();
494
+ } else if (b === 45) {// '-'
495
+ is_neg = true;
496
+ b = this.ctx.getByte();
497
+ }
498
+
499
+ mantisa = Number(this.readInt());
500
+ b = this.ctx.peekByte();
501
+ (() => {
502
+ while (true) {
503
+ switch (b) {
504
+ case 'u'.codePointAt(0): {
505
+ is_uint = true;
506
+ this.ctx.getByte();
507
+ return;
508
+ }
509
+ case '.'.codePointAt(0): {
510
+ is_decimal = true;
511
+ this.ctx.getByte();
512
+ const ix1 = this.ctx.index;
513
+ decimals = Number(this.readInt());
514
+ // if(n < 0)
515
+ // UNPACK_ERROR(CCPCP_RC_MALFORMED_INPUT, "Malformed number decimal part.")
516
+ dec_cnt = this.ctx.index - ix1;
517
+ b = this.ctx.peekByte();
518
+ if (b >= 0) {
519
+ continue;
520
+ }
521
+ return;
522
+ }
523
+ case 'e'.codePointAt(0):
524
+ case 'E'.codePointAt(0): {
525
+ is_decimal = true;
526
+ this.ctx.getByte();
527
+ const ix1 = this.ctx.index;
528
+ exponent = Number(this.readInt());
529
+ if (ix1 === this.ctx.index) {
530
+ throw new TypeError('Malformed number exponential part.');
531
+ }
532
+ return;
533
+ }
534
+ default:
535
+ return;
536
+ }
537
+ }
538
+ })();
539
+ if (is_decimal) {
540
+ for (let i = 0; i < dec_cnt; ++i) {
541
+ mantisa *= 10;
542
+ }
543
+ mantisa += decimals;
544
+ mantisa = is_neg ? -mantisa : mantisa;
545
+ return new Decimal(mantisa, exponent - dec_cnt);
546
+ }
547
+ if (is_uint) {
548
+ return new UInt(mantisa);
549
+ }
550
+ return new Int(is_neg ? -mantisa : mantisa);
551
+ }
552
+
553
+ private implReadMap<MapType extends MetaMap | ShvMap | IMap>(MapTypeCtor: new () => MapType, terminator: number) {
554
+ const map = new MapTypeCtor();
555
+ this.ctx.getByte(); // eat start
556
+ while (true) {
557
+ this.skipWhitespace();
558
+ const b = this.ctx.peekByte();
559
+ if (b === terminator) {
560
+ this.ctx.getByte();
561
+ break;
562
+ }
563
+ const key = this.read();
564
+ if (key instanceof RpcValueWithMetaData) {
565
+ throw new TypeError('Map/IMap/MetaMap key can\'t have its own MetaData');
566
+ }
567
+
568
+ this.skipWhitespace();
569
+ const val = this.read();
570
+
571
+ if (map instanceof MetaMap && typeof key === 'string') {
572
+ map.value[key] = val;
573
+ } else if (map instanceof MetaMap && (key instanceof UInt || key instanceof Int)) {
574
+ map.value[Number(key)] = val;
575
+ } else if (map instanceof ShvMap && typeof key === 'string') {
576
+ map.value[key] = val;
577
+ } else if (map instanceof IMap && (key instanceof UInt || key instanceof Int)) {
578
+ map.value[Number(key)] = val;
579
+ } else {
580
+ throw new TypeError('Malformed map, invalid key');
581
+ }
582
+ }
583
+ return map;
584
+ }
585
+ }
586
+
587
+ class CponWriter {
588
+ ctx: PackContext;
589
+ nestLevel = 0;
590
+ constructor(private readonly indentString?: string) {
591
+ this.ctx = new PackContext();
592
+ }
593
+
594
+ write(rpc_val: RpcValue) {
595
+ if (rpc_val instanceof RpcValueWithMetaData) {
596
+ this.writeMeta(rpc_val.meta);
597
+ rpc_val = rpc_val.value;
598
+ }
599
+
600
+ switch (true) {
601
+ case rpc_val === undefined:
602
+ this.ctx.writeStringUtf8('null');
603
+ break;
604
+ case typeof rpc_val === 'boolean':
605
+ this.writeBool(rpc_val);
606
+ break;
607
+ case typeof rpc_val === 'string':
608
+ this.writeJSString(rpc_val);
609
+ break;
610
+ case rpc_val instanceof ArrayBuffer:
611
+ this.writeBlob(rpc_val);
612
+ break;
613
+ case rpc_val instanceof UInt:
614
+ this.writeUInt(rpc_val);
615
+ break;
616
+ case rpc_val instanceof Int:
617
+ this.writeInt(rpc_val);
618
+ break;
619
+ case rpc_val instanceof Double:
620
+ this.writeDouble(rpc_val);
621
+ break;
622
+ case rpc_val instanceof Decimal:
623
+ this.writeDecimal(rpc_val);
624
+ break;
625
+ case Array.isArray(rpc_val):
626
+ this.writeList(rpc_val);
627
+ break;
628
+ case rpc_val instanceof IMap:
629
+ this.writeIMap(rpc_val);
630
+ break;
631
+ case rpc_val instanceof ShvMap:
632
+ this.writeMap(rpc_val);
633
+ break;
634
+ case rpc_val instanceof Date:
635
+ this.writeDateTime(rpc_val);
636
+ break;
637
+ default:
638
+ console.log('Can\'t serialize rpc value', rpc_val);
639
+ throw new Error('Can\'t serialize rpc value');
640
+ }
641
+ }
642
+
643
+ writeJSString(str: string) {
644
+ this.ctx.writeStringUtf8('"');
645
+ for (let i = 0; i < str.length; i++) {
646
+ const charcode = str.codePointAt(i)!;
647
+ switch (charcode) {
648
+ case 0:
649
+ this.ctx.writeStringUtf8('\\0');
650
+ break;
651
+ case '\\'.codePointAt(0):
652
+ this.ctx.writeStringUtf8('\\\\');
653
+ break;
654
+ case '\t'.codePointAt(0):
655
+ this.ctx.writeStringUtf8('\\t');
656
+ break;
657
+ case '\b'.codePointAt(0):
658
+ this.ctx.writeStringUtf8('\\b');
659
+ break;
660
+ case '\r'.codePointAt(0):
661
+ this.ctx.writeStringUtf8('\\r');
662
+ break;
663
+ case '\n'.codePointAt(0):
664
+ this.ctx.writeStringUtf8('\\n');
665
+ break;
666
+ case '"'.codePointAt(0):
667
+ this.ctx.writeStringUtf8('\\"');
668
+ break;
669
+ default:
670
+ this.ctx.writeCharCodeUtf8(charcode);
671
+ }
672
+ }
673
+ this.ctx.writeStringUtf8('"');
674
+ }
675
+
676
+ writeBlob(buffer: ArrayBuffer) {
677
+ this.ctx.writeStringUtf8('b"');
678
+ const data = new Uint8Array(buffer);
679
+ for (const b of data) {
680
+ switch (b) {
681
+ case '\\'.codePointAt(0):
682
+ this.ctx.writeStringUtf8('\\\\');
683
+ break;
684
+ case '\t'.codePointAt(0):
685
+ this.ctx.writeStringUtf8('\\t');
686
+ break;
687
+ case '\r'.codePointAt(0):
688
+ this.ctx.writeStringUtf8('\\r');
689
+ break;
690
+ case '\n'.codePointAt(0):
691
+ this.ctx.writeStringUtf8('\\n');
692
+ break;
693
+ case '"'.codePointAt(0):
694
+ this.ctx.writeStringUtf8('\\"');
695
+ break;
696
+ default:
697
+ if (b >= 32 && b < 127) {
698
+ this.ctx.putByte(b);
699
+ } else {
700
+ this.ctx.putByte('\\'.codePointAt(0)!);
701
+ this.ctx.putByte(hexify(b / 16));
702
+ this.ctx.putByte(hexify(b % 16));
703
+ }
704
+ }
705
+ }
706
+ this.ctx.writeStringUtf8('"');
707
+ }
708
+
709
+ writeDateTime(dt: DateTime) {
710
+ if (!dt) {
711
+ this.ctx.writeStringUtf8('d""');
712
+ return;
713
+ }
714
+ const epoch_msec = dt.getTime();
715
+ let utc_offset = dt.utc_offset;
716
+ const local_msec = epoch_msec + (60_000 * (utc_offset ?? 0));
717
+ let s = new Date(local_msec).toISOString();
718
+ const rtrim = (local_msec % 1000) ? 1 : 5;
719
+ this.ctx.writeStringUtf8('d"');
720
+ for (let i = 0; i < s.length - rtrim; i++) {
721
+ this.ctx.putByte(s.codePointAt(i)!);
722
+ }
723
+ if (utc_offset === undefined || utc_offset === 0) {
724
+ this.ctx.writeStringUtf8('Z');
725
+ } else {
726
+ if (utc_offset < 0) {
727
+ this.ctx.writeStringUtf8('-');
728
+ utc_offset = -utc_offset;
729
+ } else {
730
+ this.ctx.writeStringUtf8('+');
731
+ }
732
+ s = (Math.trunc(utc_offset / 60)).toString().padStart(2, '0');
733
+ if (utc_offset % 60) {
734
+ s += (utc_offset % 60).toString().padStart(2, '0');
735
+ }
736
+ for (let i = 0; i < s.length; i++) {
737
+ this.ctx.putByte(s.codePointAt(i)!);
738
+ }
739
+ }
740
+ this.ctx.writeStringUtf8('"');
741
+ }
742
+
743
+ writeBool(b: boolean) {
744
+ this.ctx.writeStringUtf8(b ? 'true' : 'false');
745
+ }
746
+
747
+ writeMeta(map: MetaMap) {
748
+ this.writeMapContent(map, '<', '>');
749
+ }
750
+
751
+ writeIMap(map: IMap) {
752
+ this.writeMapContent(map, 'i{', '}');
753
+ }
754
+
755
+ writeMap(map: ShvMap) {
756
+ this.writeMapContent(map, '{', '}');
757
+ }
758
+
759
+ writeMapContent(map: MetaMap | ShvMap | IMap, delimiterStart: string, delimiterEnd: string) {
760
+ this.ctx.writeStringUtf8(delimiterStart);
761
+ this.increaseIndentIfNotOneLiner(map);
762
+ this.doIndentIfNotOneliner(map);
763
+ let first = true;
764
+ for (const [key, value] of Object.entries(map.value)) {
765
+ if (value === undefined) {
766
+ continue;
767
+ }
768
+ if (!first) {
769
+ this.ctx.putByte(','.codePointAt(0)!);
770
+ this.doIndentIfNotOneliner(map);
771
+ }
772
+ first = false;
773
+
774
+ if (map instanceof IMap) {
775
+ const int_key = Number(key);
776
+ if (Number.isNaN(int_key)) {
777
+ throw new TypeError('Invalid NaN IMap key');
778
+ }
779
+
780
+ this.writeInt(new Int(int_key));
781
+ } else if (map instanceof MetaMap) {
782
+ const int_key = Number(key);
783
+ if (Number.isNaN(int_key)) {
784
+ this.ctx.putByte('"'.codePointAt(0)!);
785
+ this.ctx.writeStringUtf8(key.toString());
786
+ this.ctx.putByte('"'.codePointAt(0)!);
787
+ } else {
788
+ this.writeInt(new Int(int_key));
789
+ }
790
+ } else {
791
+ this.writeJSString(key.toString());
792
+ }
793
+
794
+ this.ctx.writeStringUtf8(':');
795
+ this.write(value);
796
+ }
797
+
798
+ this.decreaseIndentIfNotOneLiner(map);
799
+ this.doIndentIfNotOneliner(map);
800
+ this.ctx.writeStringUtf8(delimiterEnd);
801
+ }
802
+
803
+ writeList(lst: RpcValue[]) {
804
+ this.ctx.putByte('['.codePointAt(0)!);
805
+ this.increaseIndentIfNotOneLiner(lst);
806
+ this.doIndentIfNotOneliner(lst);
807
+ for (const [i, element] of lst.entries()) {
808
+ if (i > 0) {
809
+ this.ctx.putByte(','.codePointAt(0)!);
810
+ this.doIndentIfNotOneliner(lst);
811
+ }
812
+ this.write(element);
813
+ }
814
+ this.decreaseIndentIfNotOneLiner(lst);
815
+ this.doIndentIfNotOneliner(lst);
816
+ this.ctx.putByte(']'.codePointAt(0)!);
817
+ }
818
+
819
+ writeUInt(num: UInt) {
820
+ const s = Number(num).toString();
821
+ this.ctx.writeStringUtf8(s);
822
+ this.ctx.putByte('u'.codePointAt(0)!);
823
+ }
824
+
825
+ writeInt(num: Int) {
826
+ const s = Number(num).toString();
827
+ this.ctx.writeStringUtf8(s);
828
+ }
829
+
830
+ writeDouble(num: Double) {
831
+ let s = num.value.toString();
832
+ if (!s.includes('.')) {
833
+ s += '.';
834
+ }
835
+ this.ctx.writeStringUtf8(s);
836
+ }
837
+
838
+ writeDecimal(val: Decimal) {
839
+ let mantisa = val.mantisa;
840
+ const exponent = val.exponent;
841
+ if (mantisa < 0) {
842
+ mantisa = -mantisa;
843
+ this.ctx.putByte('-'.codePointAt(0)!);
844
+ }
845
+ let str = mantisa.toString();
846
+ const n = str.length;
847
+ const dec_places = -exponent;
848
+ if (dec_places > 0 && dec_places < n) {
849
+ const dot_ix = n - dec_places;
850
+ str = str.slice(0, dot_ix) + '.' + str.slice(dot_ix);
851
+ } else if (dec_places > 0 && dec_places <= 3) {
852
+ const extra_0_cnt = dec_places - n;
853
+ let str0 = '0.';
854
+ for (let i = 0; i < extra_0_cnt; ++i) {
855
+ str0 += '0';
856
+ }
857
+ str = str0 + str;
858
+ } else if (dec_places < 0 && n + exponent <= 9) {
859
+ for (let i = 0; i < exponent; ++i) {
860
+ str += '0';
861
+ }
862
+ str += '.';
863
+ } else if (dec_places === 0) {
864
+ str += '.';
865
+ } else {
866
+ str += 'e' + exponent;
867
+ }
868
+ for (let i = 0; i < str.length; ++i) {
869
+ this.ctx.putByte(str.codePointAt(i)!);
870
+ }
871
+ }
872
+
873
+ private doIndentIfNotOneliner(map: MetaMap | ShvMap | IMap | List) {
874
+ if (this.indentString !== undefined && !this.isOneLiner(map)) {
875
+ this.ctx.putByte('\n'.codePointAt(0)!);
876
+ this.ctx.writeStringUtf8(this.indentString.repeat(this.nestLevel));
877
+ }
878
+ }
879
+
880
+ private isOneLiner(value: MetaMap | ShvMap | IMap | List) {
881
+ const keyThreshold = Array.isArray(value) ? 10 : 5;
882
+
883
+ if (Array.isArray(value)) {
884
+ return value.length <= keyThreshold && !(value.some(x => Array.isArray(x) || x instanceof ShvMap || x instanceof IMap));
885
+ }
886
+
887
+ return Object.keys(value.value).length <= keyThreshold && !(Object.values(value.value).some(x => Array.isArray(x) || x instanceof ShvMap || x instanceof IMap));
888
+ }
889
+
890
+ private increaseIndentIfNotOneLiner(value: MetaMap | ShvMap | IMap | List) {
891
+ if (!this.isOneLiner(value)) {
892
+ this.nestLevel++;
893
+ }
894
+ }
895
+
896
+ private decreaseIndentIfNotOneLiner(value: MetaMap | ShvMap | IMap | List) {
897
+ if (!this.isOneLiner(value)) {
898
+ this.nestLevel--;
899
+ }
900
+ }
901
+ }
902
+
903
+ const toCpon = (value: RpcValue, indentString?: string) => {
904
+ const wr = new CponWriter(indentString);
905
+ wr.write(value);
906
+ return new TextDecoder().decode(wr.ctx.buffer());
907
+ };
908
+
909
+ const fromCpon = (str: string | Uint8Array) => {
910
+ if (typeof str === 'string') {
911
+ str = new TextEncoder().encode(str);
912
+ }
913
+
914
+ const rd = new CponReader(new UnpackContext(str.buffer));
915
+ return rd.read();
916
+ };
917
+
918
+ const CponProtocolType = 2;
919
+
920
+ export {utf8ToString, CponWriter, CponReader, CponProtocolType, toCpon, fromCpon};