-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy path$$.ByteStream.jsxlib
More file actions
1560 lines (1330 loc) · 55.1 KB
/
$$.ByteStream.jsxlib
File metadata and controls
1560 lines (1330 loc) · 55.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
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
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*******************************************************************************
Name: ByteStream
Desc: Manage a byte stream (for either input or output.)
Path: /etc/$$.ByteStream.jsxlib
Require: Ext/
Encoding: ÛȚF8
Core: NO
Kind: Class.
API: onEngine() isTag() isFormat() sizeOf() encode() ; ByteStream->[[global]]
[proto] =create() valueOf() getValue() getSource() getIndex() getBytes()
toString() toSource() jump() clone() backup() restore()
read() read(U|I)(08|16|32)()
peek() peek(U|I)(08|16|32)()
write() write(U|I)(08|16|32)()
+ Array.prototype
DOM-access: NO
Todo: Should the 'HEX' tag support Little-Endian?
Created: 180927 (YYMMDD)
Modified: 250802 (YYMMDD)
*******************************************************************************/
;$$.hasOwnProperty('ByteStream') || eval(__(CLASS, $$, 'ByteStream', 250802))
//==========================================================================
// NOTICE
//==========================================================================
/*
1. PURPOSE
____________________________________________________________________________
The present class makes it easy to read or write a stream of bytes (8-bit
codes.) Its main purpose is to handle binary data stored in files.
Input streams (IStreams) are supplied in either Array or String form,
output streams (OStreams) are managed in Array form -- but you can use
`this.toString()` to get the corresponding byte string.
- To create an IStream, use `new ByteStream(input)`, where `input`
is either an Array of bytes, or a String of byte codes. The stream
instance will then support `peek...()` and `read...()` methods.
- To create an OStream, just use `new ByteStream()` with no argument.
The stream instance will then support the `write()` method.
STREAM TYPE | ACCESS METHODS | SHARED METHODS
---------------|-----------------|-------------------------------------------
IStream | peek() read() |
---------------|-----------------| jump() valueOf() toString() getBytes() etc
OStream | write() |
---------------|-----------------|-------------------------------------------
This module performs conversions between JS values and various byte-encoded
structures (signed and unsigned char, short, long, float, etc.), in either
Big-Endian (BE) or Little-Endian (LE) order.
[REM] In big-endian format (BE) the most significant byte is stored
or sent first. This is the default order.
2. SUPPORTED ENCODING TAGS
____________________________________________________________________________
As suggested in <github.com/pgriess/node-jspack>, format strings are used
as compact descriptions of the structures to be encoded or decoded. For
example, `myIStream.read("ASC U16 F32", myDestArr)` will load in the des-
tination array three decoded items: one ascii character (1 byte), one
unsigned short integer (2 bytes), and one float32 (4 bytes.)
Here are the supported tags so far:
Tag | Bytes | JS Type | Description
-----------------------------------------------------------------------
STR | n | string(n) | Raw string based on n bytes.
HEX¹ | n | string(2n)| Hex string (2n chars) ; e.g [0x1F,0x2A] -> "1F2A"
TAG | 4 | string(4) | OTF or Adobe tag, i.e string of 4 Ascii chars [ADD200420]
ASC | 1 | string(1) | Ascii character ; char code <= 0x7F
LAT | 1 | string(1) | Latin character ; char code <= 0xFF
-----------------------------------------------------------------------
I08 | 1 | number | Signed byte [-128, 127 ]
U08 | 1 | number | Unsigned byte [ 0, 255 ]
I16 | 2 | number | Signed short [-32768, 32767 ]
U16 | 2 | number | Unsigned short [ 0, 65535 ]
I24 | 3 | number | Signed int24 [-0x800000, 0x7FFFFF ]
U24 | 3 | number | Unsigned int24 [ 0, 0xFFFFFF ]
I32 | 4 | number | Signed long [-0x80000000, 0x7FFFFFFF ]
U32 | 4 | number | Unsigned long [ 0, 0xFFFFFFFF ]
-----------------------------------------------------------------------
FXP | 4 | number | Signed 16.16 Fixed Point [-32768, 32767.99998474121]
UFX | 4 | number | Unsigned 16.16 Fixed Pnt [ 0, 65535.9999847412 ]
F2D | 2 | number | 2.14 signed fixed number [ -2, +1.999938965 ]
| w/ low 14 bits of frac
-----------------------------------------------------------------------
F32² | 4 | number | Float (32bits) ; IEEE754-32
F64 | 8 | number | Double (64bits) ; IEEE754-64
¹ About the HEX tag.
• In IStreams, the HEX tag READS 1 byte (e.g 0x3A) from the stream
and produces a 2-character string ("3A"), uppercase, in the output
structure.
• In OStreams, the HEX tag expects a valid 2-character string from
the source (e.g "3a") and WRITES 1 byte (0x3A) in the stream.
• By default HEX consumes one byte (2 hex chars) but it deals
with the `*<COUNT>` syntax (see Section 3.) as the STR tag does,
that is, HEX*3 produces (IStream) or expects (OStream) a single
6-character string rather than 3 separate strings. [CHG250226]
² Since JS doesn't natively support 32-bit floats, whenever a float
is stored the source number must be rounded. See the IEEE754_32
routines in Ext/$$.number for implementation detail.
3. SYNTAX OF ENCODING SCHEMES
____________________________________________________________________________
A format string specifies a sequence of one or several 'encoding schemes'
separated by a space character:
<FORMAT_STRING> := <ENCODING_SHEME>(` `<ENCODING_SCHEME>)*
Each encoding scheme specifies one encoding tag followed by optional
parameters for byte ordering, count, and key assignment:
<ENCODING_SCHEME> := <ENC_TAG><ORDER>?(`*`<COUNT>)?(`:`<KEY>)?
where
<ENC_TAG> is one of the defined encoding tags
("STR", "ASC", etc.)
<ORDER> is either `>` for Big-Endian, `<` for Little-
Endian, or empty (BE is assumed.)
<COUNT> is a sequence of decimal digits that determines
the number of elements to read or write. For
example, `U16*3` indicates that three unsigned
shorts have to be processed.
If <COUNT> is missing, 1 is assumed.
[REM] If <COUNT> is `0`, no element is read/written
from/to the stream.
<KEY> is a property name to read from or to write to.
For example, `F64:value` indicates that a double
has to be loaded from the `value` key (while wri-
ting an OStream), or loaded in the `value` key
(while reading an IStream.)
'0' is allowed as a special <KEY> and has the
following behavior: in IStreams, it 'reads'
the specified element BUT data aren't recorded
in the output structure ; in OStreams, the '0'
<KEY> pushes the corresponding \0 bytes regard-
less of the input structure.
[REM] Unless it is '0', no <KEY> can start with a digit
(syntax error).
When no <KEY> is provided, data are sequentially loaded from/to using
automatic indices. IStreams load data in `this` (which behave as an
Array) unless a destination object is provided by the `peek()` or
`read()` methods. OStreams get data from the 2nd argument of the
`write()` method.
Examples:
iStream.read("U16 F32*2")
--------------------------------------------------
Read a long and two floats from the byte stream,
then push those 3 values in the `this` array.
iStream.read("U16 F32*2", dest)
--------------------------------------------------
Read a long and two floats from the byte stream,
then push those 3 values in `dest`.
oStream.write("U16 F32*2", source)
--------------------------------------------------
Write a long and two floats in the byte stream,
getting values from source[0], source[1] and source[2].
iStream.read("U16:count F32*2")
--------------------------------------------------
Read a long and two floats from the byte stream,
store the long as `this.count`, and push the two
float numbers in the `this` array.
iStream.read("U16:0 F32*2")
--------------------------------------------------
Read a long and two floats from the byte stream,
ignore the long number (:0), and push the two
float numbers in the `this` array.
iStream.read("U16:count F32*2", dest)
--------------------------------------------------
Read a long and two floats from the byte stream,
store the long as `dest.count`, and push the two
float numbers in `dest`.
oStream.write("U16:count F32*2", source)
--------------------------------------------------
Write a long and two floats in the byte stream,
getting the long from `source.count` and the
float numbers from source[0] and source[1].
oStream.write("U16:0 F32*2", source)
--------------------------------------------------
Write a zero long (U16:0) and two floats in the byte
stream, getting the float numbers from source[0] and
source[1] -- source is not intended to deliver the
U16 element.
The API allows (and assumes) that the destination or source argument
is either a simple Array, a key-value Object, or a mixed type
combining Array and Object features. However, *pushing* data in the
`dest` object can only work as expected if it is an Array instance.
If it is not, the keys '0', '1', '2'... will be re-used at each
reading stage. Also, while writing an OStream from a source object
which is an Array instance, data are (re)processed from index 0 at
each new writing stage.
[REM] In case the `source` argument is a scalar, the `write` method
consider it an array of one element, so `oStream.write("F64", 12.345)`
still works as expected.
<COUNT> and <KEY> parameters can be combined. In such case, `obj[key]`
is treated as a sub-array for loading data from/to. For example,
iStream.read("U08*3:rgb", dest)
--------------------------------------------------
Read three integers from the byte stream and
store those values in dest.rgb[0], dest.rgb[1],
dest.rgb[2]. If dest.rgb doesn't exist, the
array is created.
In the above example, note that the values won't be *pushed* on
`dest.rgb` if such array already exists with some elements: the
start index is always reset to zero.
Similarly,
oStream.write("U08*3:rgb", source)
--------------------------------------------------
Write three bytes from `source.rgb[0]`,
`source.rgb[1]` and `source.rgb[2]`.
It is the responsability of the client code to supply a `source`
object that fits the format string. Whenever data cannot be found
as specified, zero bytes (0x00) are sent to the stream.
SPECIAL CASES: `STR` and `HEX` TAGS (strings)
• The `STR` tag allows to quickly handle fixed length strings. The scheme
`STR*nn` is then interpreted s.t. it loads a single string formed of
`nn` characters (rather than an array of `nn` single-character strings.)
• Similarly, the `HEX` tag interprets the scheme `HEX*nn` s.t. it loads
a single string formed of 2*nn characters (each elementary hex string
having two hexadecimal digits.)
`iStream.read("STR*10:name", dest)` will read and load a 10-character
string in dest.name.
`iStream.read("HEX*3:color", dest)` will read and load a 6-character
string (3×2) in dest.color, e.g "3A692B".
`oStream.write("STR*10", "hello")` will write 10 bytes, five bytes from
`hello` and five zero bytes. Note that `write("STR*3", "hello")`
would truncate the string to its three fist characters.
`oStream.write("HEX*3", "3a62")` will write 3 bytes (0x00,0x3A,0x62),
as "3a62" is then regarded as "003A62" (left padding.)
[REM] When `*<COUNT>` is missing, the `write` method considers the
actual string length. For example, `write("STR", "hello")` will exactly
write 5 bytes, and an empty string is transparent (i.e. no write is
performed upon the OStream.) By contrast, `read("STR",dest)` just reads
a *single character* from the IStream, as would do "STR*1".
*/
//==========================================================================
// TOOLS: GETA() GETS() BYBE() BYLE()
//==========================================================================
[PRIVATE]
({
GETA: function(/*byte[]*/a,/*uint*/p)
//----------------------------------
// (Get-Byte-from-Array.)
// => byte
{
return 0xFF&a[p];
},
GETS: function(/*str*/s,/*uint*/p)
//----------------------------------
// (Get-Byte-from-String.)
// => byte
{
return 0xFF&s.charCodeAt(p);
},
BYBE: function(/*byte[]&*/q,/*uint*/x,/*2..4*/n, i)
//----------------------------------
// (Set-Bytes-Big-Endian.)
// => q
{
for( --n, i=-1 ; ++i <= n ; q[i]=0xFF&(x>>>(8*(n-i))) );
return q;
},
BYLE: function(/*byte[]&*/q,/*uint*/x,/*2..4*/n, i)
//----------------------------------
// (Set-Bytes-Little-Endian.)
// => q
{
for( i=-1 ; ++i < n ; q[i]=0xFF&(x>>>(8*i)) );
return q;
},
})
//==========================================================================
// BUILT-IN ENCODERS / DECODERS
//==========================================================================
[PRIVATE]
({
SPCS: / +/g,
// Encoding schemes.
// [ADD200420] Added `TAG` encoding (OTF tag, i.e ASC*4)
// [ADD250224] Added FXP/UFX encodings (fixed-point 16.16, signed/unsigned)
// ---
ENCS: /(STR|HEX|TAG|ASC|LAT|I08|U08|I16|U16|I24|U24|I32|U32|FXP|UFX|F2D|F32|F64)([><])?(\*\d+)?(\:[^ ]+)?/g,
// STR =================================================================
RSTR: function(/*byte[]|str*/a,/*uint*/p)
//----------------------------------
// Interprets a[p] and next bytes as raw string codepoints.
// `callee.COUNT` (set by the caller) determines the length
// of the slice. (Rem: If COUNT is zero, returns ''.)
// => str
{
a = a.slice(p, p+callee.COUNT);
return 'string'==typeof a ? a : String.fromCharCode.apply(0,a);
}
.setup({ SZ:1, COUNT:1, ZERO:'' }),
WSTR: function(/*str*/x, a,i)
//----------------------------------
// Given a string of length n, return a sequence of n bytes.
// If callee.COUNT is nonzero, use it as the result length
// instead of x.length. (Rem: if x is '', returns [].)
// => byte[n]
{
for( a=Array(i=callee.COUNT||x.length) ; i-- ; a[i]=0xFF&x.charCodeAt(i) );
return a;
}
.setup({ COUNT:0 }),
// HEX =================================================================
RHEX: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f, n,r,i)
//----------------------------------
// Converts a[p] and next bytes into 2-char hexadecimal forms
// and returns the resulting string (2 chars for each byte).
// `callee.COUNT` (set by the caller) determines the number
// of bytes (n) so the final string has 2n characters.
// (Rem: If COUNT is zero, returns ''.)
// E.g. with COUNT==3, [0x3A,0x69,0x1b] => "3A691B"
// => str
{
n = callee.COUNT;
r = '';
for
(
i=-1 ; ++i < n ;
r += ('0'+(0xFF&f(a,p+i)).toString(16)).slice(-2)
);
return r.toUpperCase();
}
.setup({ SZ: 1, COUNT:1, ZERO:'00' }),
WHEX: function(/*str*/x, n,a,i)
//----------------------------------
// Given an 2n-character string formed of hex digits -- i.e ASC
// chars in [0-1A-Fa-f] --, return a volatile sequence of n bytes.
// E.g "3A693f..." -> [0x3A,0x69,0x3F...]
// If callee.COUNT is nonzero, use it as the result length
// instead of x.length/2. (Rem: if x is '', returns [].)
// => byte[n]
{
n = callee.COUNT || (Math.ceil(x.length/2));
if( !n ) return [];
// Make sure the string has at least 2*n characters (left-pad).
0 < (i=2*n-x.length) && (x=Array(1+i).join('0')+x);
for( a=Array(i=n) ; i-- ; a[i] = 0xFF&parseInt(x.slice(2*i,2+2*i),16) );
return a;
}
.setup({ COUNT:0 }),
// ASC =================================================================
RASC: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f)
//----------------------------------
// Interprets a[p] as an ascii char.
// => char
{
return String.fromCharCode(0x7F&f(a,p));
}
.setup({ SZ:1, ZERO:'' }),
WASC: function(/*char=\x00*/x)
//----------------------------------
// Given an ascii char, return a volatile sequence of 1 byte.
// => byte[1] [VOLATILE]
{
return (callee.Q[0]=0x7F&x.charCodeAt(0)), callee.Q;
}
.setup({ Q: Array(1) }),
// TAG =================================================================
RTAG: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f)
//----------------------------------
// [ADD200420] Interprets a[p]..a[p+3] as an OTF tag (=ASC*4).
// => str4
{
return String.fromCharCode(0x7F&f(a,p),0x7F&f(a,1+p),0x7F&f(a,2+p),0x7F&f(a,3+p));
}
.setup({ SZ:4, ZERO:'' }),
WTAG: function(/*str=\x00\x00\x00\x00*/x, q)
//----------------------------------
// [ADD200420] Given an OTF tag string (=ASC*4), return a VOLATILE sequence of 4 bytes.
// => byte[4] [VOLATILE]
{
return (q=callee.Q), (q[0]=0x7F&x.charCodeAt(0)), (q[1]=0x7F&x.charCodeAt(1)), (q[2]=0x7F&x.charCodeAt(2)), (q[3]=0x7F&x.charCodeAt(3)), q;
}
.setup({ Q: Array(4) }),
// LAT =================================================================
RLAT: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f)
//----------------------------------
// Interprets a[p] as a latin char.
// => char
{
return String.fromCharCode(0xFF&f(a,p));
}
.setup({ SZ: 1, ZERO:'' }),
WLAT: function(/*char=\x00*/x)
//----------------------------------
// Given a latin char, return a volatile sequence of 1 byte.
// => byte[1] [VOLATILE]
{
return (callee.Q[0]=0xFF&x.charCodeAt(0)), callee.Q;
}
.setup({ Q: Array(1) }),
// I08 =================================================================
RI08: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f)
//----------------------------------
// Interprets a[p] as a signed byte.
// => int8 [-0xFF,+0x7F]
{
return ( 0x80&(p=f(a,p)) ) ? -(0x100-p) : p;
}
.setup({ SZ: 1 }),
WI08: function(/*int8=0*/x)
//----------------------------------
// Given a signed int8, return a volatile sequence of 1 byte.
// => byte[1] [VOLATILE]
{
return this.WU08( 0 > (x|=0) ? (0x80|x) : x );
},
// U08 =================================================================
RU08: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f)
//----------------------------------
// Interprets a[p] as an unsigned byte.
// => uint8 [0,0xFF]
{
return f(a,p);
}
.setup({ SZ: 1 }),
WU08: function(/*uint8=0*/x)
//----------------------------------
// Given an uint8, return a volatile sequence of 1 byte.
// => byte[1] [VOLATILE]
{
return (callee.Q[0]=0xFF&x), callee.Q;
}
.setup({ Q: Array(1) }),
// I16 =================================================================
RI16: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p]) as a signed integer.
// => int16 [-0xFFFF,+0x7FFF]
{
return ( 0x8000&(p=this.RU16(a,p,f,LE)) ) ? (0xFFFF0000|p) : p;
}
.setup({ SZ: 2 }),
WI16: function(/*int16=0*/x,/*bool*/LE)
//----------------------------------
// Given a signed int16, return a volatile sequence of 2 bytes.
// => byte[2] [VOLATILE]
{
return this.WU16( 0 > (x|=0) ? (0x8000|x) : x, LE ); // [FIX250226] `LE` arg wasn't transmitted!
},
// U16 =================================================================
RU16: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p]) as an unsigned integer.
// => uint16 [0,0xFFFF]
{
return LE ?
( (f(a,1+p)<<8) | f(a,p) ):
( (f(a,p)<<8) | f(a,1+p) );
}
.setup({ SZ: 2 }),
WU16: function(/*uint16=0*/x,/*bool*/LE)
//----------------------------------
// Given an uint16, return a volatile sequence of 2 bytes.
// => byte[2] [VOLATILE]
{
return this[LE?'BYLE':'BYBE'](callee.Q,0|x,2);
}
.setup({ Q: Array(2) }),
// F2D [ADD250225] =====================================================
RF2D: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p]) as a signed 'F2DOT14' (2.14 representation.)
// => float in [-2, 1.999938965]
{
return this.RI16(a,p,f,LE)/0x4000;
}
.setup({ SZ: 2 }),
WF2D: function(/*-2..1.999938965*/x,/*bool*/LE)
//----------------------------------
// Given a signed float in [-2, 1.999938965], return a volatile sequence
// of 2 bytes carrying its F2DOT14 (2.14) representation (signed).
// => byte[2] [VOLATILE]
{
return this.WU16( 0|Math.round(0x4000*(x||0)), LE );
},
// I24 =================================================================
RI24: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p]) as a signed int24.
// => int24 [-0xFFFFFF,+0x7FFFFF]
{
return ( 0x800000&(p=this.RU24(a,p,f,LE)) ) ? (0xFF000000|p) : p;
}
.setup({ SZ: 3 }),
WI24: function(/*int24=0*/x,/*bool*/LE)
//----------------------------------
// Given a signed int24, return a volatile sequence of 3 bytes.
// => byte[3] [VOLATILE]
{
return this.WU24( 0 > (x|=0) ? (0x800000|x) : x, LE ); // [FIX250226] `LE` arg wasn't transmitted!
},
// U24 =================================================================
RU24: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p]) as an unsigned int24.
// => uint24 [0,0xFFFFFF]
{
return LE ?
( (f(a,2+p)<<16) | (f(a,1+p)<<8) | f(a,p) ):
( (f(a,p)<<16) | (f(a,1+p)<<8) | f(a,2+p) );
}
.setup({ SZ: 3 }),
WU24: function(/*uint24=0*/x,/*bool*/LE)
//----------------------------------
// Given an uint24, return a volatile sequence of 3 bytes.
// => byte[3] [VOLATILE]
{
return this[LE?'BYLE':'BYBE'](callee.Q,x||0,3);
}
.setup({ Q: Array(3) }),
// I32 =================================================================
RI32: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p],a[3+p]) as a signed long.
// => int32 [-0xFFFFFFFF,+0x7FFFFFFF]
{
return LE ?
( (f(a,3+p)<<24) | (f(a,2+p)<<16) | (f(a,1+p)<<8) | f(a,p) ):
( (f(a,p)<<24) | (f(a,1+p)<<16) | (f(a,2+p)<<8) | f(a,3+p) );
}
.setup({ SZ: 4 }),
WI32: function(/*int32=0*/x,/*bool*/LE)
//----------------------------------
// Given a signed long, return a volatile sequence of 4 bytes.
// => byte[4] [VOLATILE]
{
return this.WU32( x, LE );
},
// U32 =================================================================
RU32: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p],a[3+p]) as an unsigned long.
// => uint32 [0,0xFFFFFFFF]
{
return this.RI32(a,p,f,LE) >>> 0;
}
.setup({ SZ: 4 }),
WU32: function(/*uint32=0*/x,/*bool*/LE)
//----------------------------------
// Given an unsigned long, return a volatile sequence of 4 bytes.
// => byte[4] [VOLATILE]
{
return this[LE?'BYLE':'BYBE'](callee.Q,x||0,4);
}
.setup({ Q: Array(4) }),
// FXP [ADD250224] =====================================================
RFXP: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p],a[3+p]) as a signed fixed-point number (16.16 representation.)
// => float in [-32768, 32767.99998474121]
{
// 16-bit integer part 16-bit frac part
return this.RI16(a,(LE?2+p:p),f,LE) + this.RU16(a,(LE?p:2+p),f,LE)/0x10000;
}
.setup({ SZ: 4 }),
WFXP: function(/*flo=0*/x,/*bool*/LE)
//----------------------------------
// Given a signed float in [-32768, 32767.99998474121], return a volatile sequence
// of 4 bytes carrying its fixed-point 16.16 representation (signed).
// => byte[4] [VOLATILE]
{
return this.WU32( 0|Math.round(0x10000*(x||0)), LE );
},
// UFX [ADD250224] =====================================================
RUFX: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p],a[3+p]) as an unsigned fixed-point number (16.16 representation.)
// => float in [0, 65535.9999847412]
{
// 16-bit integer part 16-bit frac part
return this.RU16(a,(LE?2+p:p),f,LE) + this.RU16(a,(LE?p:2+p),f,LE)/0x10000;
}
.setup({ SZ: 4 }),
WUFX: function(/*flo=0*/x,/*bool*/LE)
//----------------------------------
// Given an unsigned float in [0, 65535.9999847412], return a volatile sequence
// of 4 bytes carrying its fixed-point 16.16 representation (unsigned).
// => byte[4] [VOLATILE]
{
return this.WU32( Math.round(0x10000*(x||0))>>>0, LE );
},
// F32 =================================================================
RF32: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE)
//----------------------------------
// Interprets (a[p],a[1+p],a[2+p],a[3+p]) as a float32 (IEEE754 representation.)
// => float32
{
return Number.fromIEEE754_32( this.RU32(a,p,f,LE).toString(16) );
}
.setup({ SZ: 4 }),
WF32: function(/*flo32=0*/x,/*bool*/LE)
//----------------------------------
// Given a float32, return a volatile sequence of
// 4 bytes carrying its IEEE754-32 representation.
// => byte[4] [VOLATILE]
{
return this[LE?'BYLE':'BYBE'](callee.Q,Number('0x'+(x||0).toIEEE754_32()),4);
}
.setup({ Q: Array(4) }),
// F64 =================================================================
RF64: function(/*byte[]|str*/a,/*uint*/p,/*fct*/f,/*bool*/LE, t)
//----------------------------------
// Interprets (a[p],a[1+p],...,a[7+p]) as a float64 (IEEE754 representation.)
// => float64
{
t = ( '00000000'+this.RU32(a, LE?p:(4+p), f, LE).toString(16) ).slice(-8);
return Number.fromIEEE754( this.RU32(a, LE?(4+p):p, f, LE).toString(16) + t );
}
.setup({ SZ: 8 }),
WF64: function(/*flo64=0*/x,/*bool*/LE, q,i,p)
//----------------------------------
// Given a float64, return a volatile sequence of
// 8 bytes carrying its IEEE754 representation.
// => byte[8] [VOLATILE]
{
x = (x||0).toIEEE754(); // 16 characters ; e.g "405EDD2F1A9FBE77"
for( q=callee.Q, i=-1 ; ++i < 8 ; (p=2*i), (LE&&(p=14-p)), q[i]=Number('0x'+x.slice(p,2+p)) );
return q;
}
.setup({ Q: Array(8) }),
})
//==========================================================================
// READ/WRITE: LOOP() IGET()
//==========================================================================
[PRIVATE]
({
LOOP: function(/*'R'|'W'*/mode,/*str*/fmt,/*ByteStream|byte[]&*/BS,/*(obj|arr|BS)&*/what, dst,src,idx,p,f,z,v,re,m,F,R,dp,LE,n,k,ZK,ZV,INC,a,i,o)
//----------------------------------
// READ mode: what::dst ; idx=what.length||0 ; init. pos=BS.__pos__
// WRITE mode: what::src ; idx=0 ; init. pos=0
// [ADD250302] In 'R' mode, `BS` may be a simple array of bytes rather
// than a ByteStream instance: used internally by static `encode()`.
// ---
// this :: ~
// => { pos:uint, val:?any }& [VOLATILE]
{
const READ = +('R'==mode); // READ :: 1|0
const PUSH = !READ && Array.prototype.push; // Only needed in WRITE mode
const DF_COUNT = READ ? 1 : 0; // Used when F.COUNT is defined.
if( READ )
{
dst = what;
src = BS.__src__;
idx = dst instanceof Array ? dst.length : 0;
p = BS.__pos__;
f = this[BS.__get__]; // Adjust getter based on __scr__ type.
}
else
{
dst = BS;
src = what;
idx = 0;
p = 0;
f = false;
}
for( z=0, v=void 0, (re=this.ENCS).lastIndex=0 ; m=re.exec(fmt) ; )
{
// m :: [ match, tag, ?('>'|'<'), ?'*<num>', ?':<key>' ]
F = this[mode+m[1]]; // F :: fct ; read or write function
if( 'function' != typeof F ) continue; // Shouldn't happen.
R = READ ? F : this['R'+m[1]]; // R :: read func.
dp = R.SZ; // dp :: uint ; unit size.
LE = '<'===m[2]; // Little-endian flag. If missing, BE assumed.
n = (n=m[3]) ? parseInt(n.slice(1),10) : false; // n :: uint|false ; <COUNT> param (false if missing)
k = (k=m[4]) ? k.slice(1) : false; // k :: str|false ; <KEY> param (false if missing)
ZK = k && 0x30 <= (t=k.charCodeAt(0)) && t <= 0x39; // ZK :: bool ; if k starts with a digit it MUST
if( ZK && '0' !== k ) continue; // be '0' otherwise skip that subscheme! [CHG250224]
++z; // Number of valid subschemes.
// ---
// 1. Meaning of Zero-Key (ZK) e.g 'U08:0', 'STR*3:0'
// READ: Consume the IStream element(s) + shift pos
// but don't save anything in the dest.
// -> INC=0
// WRITE: Don't load anything from the source
// but push sized ZERO bytes to the OStream.
// -> INC=0 ; ZERO needed
// (In both cases the stream is affected.)
//
// 2. Meaning of Zero-Count (n===0) e.g 'U16*0', 'STR*0:s'
// READ: Don't consume any IStream element (fixed pos)
// but save the corresponding ZERO in dest and
// update idx if relevant. [Do nothing if ZK.]
// -> INC=auto ; ZERO needed
// WRITE: Get nothing from source and don't push any
// byte to the OStream, but update idx if relevant.
// [Do nothing if ZK.]
// -> INC=auto
// (In both cases the stream is NOT affected.)
// ---
ZV = ( 0===n || ZK ) && // In case it would serve:
( R.hasOwnProperty('ZERO') ? R.ZERO : 0 ); // ZERO value as told by the reader (default: 0.)
INC = +(false===k); // 0|1 ; auto-increment iff no <KEY> param. (Rem: 0 if ZK.)
if( 0===n ) // Deals with Zero-Count -> NO STREAM ACCESS.
{
if( ZK ) continue; // Do nothing if Zero-Key.
READ && (dst[INC?idx:k]=ZV); // In READ mode load ZERO in dest key; in WRITE mode noop.
idx += INC; // Increment idx iff k===false.
continue;
}
if( F.hasOwnProperty('COUNT') ) // If available, the special F.COUNT prop is preset
{ // to n (if non-false) or defaulted to DF_COUNT.
F.COUNT = false===n ? DF_COUNT : n; // DF_COUNT is 1 in READ case ; 0 in WRITE case, meaning automatic sizing.
dp && (dp*=F.COUNT); // Upscale `dp` accordingly (irrelevant in WRITE case.)
n = 1; // Reset n to 1.
}
false===n && (n=1); // Now we can default n to 1 if it remains unspecified.
if( 1===n || ZK ) // Manage one element and/or Zero-Key.
{
i = INC ? idx : k; // Current index or key.
if( READ )
{
while( n-- )
{
v = F.call(this,src,p,f,LE); // READ one element from the stream (even if ZK, so v is updated!)
p += dp; // Shift stream position.
ZK || (dst[i]=v); // Hit the destination iff non-zero KEY.
}
}
else
{
v = ZK ? ZV : src[i]; // In WRITE mode, get one elem from src, or ZERO if ZK.
a = F.call(this,v,LE); // Get the W-result (arr).
while( n-- ){ PUSH.apply(dst, a); p+=dp; } // Push in the stream ; added `p+=dp` for consistency.
}
idx += INC; // Increment idx iff k===false. (Rem: INC is always 0 if ZK.)
continue;
}
// COUNT > 1 AND non-Zero-Key ; e.g 'I08*3', 'STR*2:s'
if( INC ) // Direct index.
{
o = what;
i = idx; // Iterate from `idx`.
}
else
{
o = what[k]; // Go into the key.
o===Object(o) || (o=what[k]=READ?[]:[o]); // Initialize to Array if non-obj (empty if dst, singleton if src).
i = 0; // Iterate from 0.
}
if( READ )
for( ; n-- ; (o[i++]=v=F.call(this,src,p,f,LE)), p+=dp );
else
for( ; n-- ; PUSH.apply(dst,F.call(this,(v=o[i++]),LE)), p+=dp ); // Added p+=dp for consistency.
INC && (idx=i);
}
// [CHG250224] Final check: if the number of valid
// subschemes (z) does not equal the number of
// space-separated substrings in fmt, the client
// code MUST be alerted that the scheme is invalid!
// ---
fmt.length && z != fmt.split(this.SPCS).length
&& error( __("Syntax error in the scheme %1.", fmt.toSource()), callee.µ );
o = callee.Q;
o.pos = p;
o.val = v;
return o;
}
.setup
({
Q: { pos:0, val:void 0 },
}),
IGET: function(/*IStream&*/BS,/*RU16|RI16...*/RK,/*bool*/LE,/*bool*/READ, v)
//----------------------------------
// [ADD250307] (Get-Value.) Utility for single peek/read actions on IStreams.
// [REM] Used by IStream shortcut methods.
// this :: ~
// => value
{
v = BS.__val__ = this[RK]( BS.__src__, BS.__pos__, this[ BS.__get__ ], LE );
READ && ( BS.__pos__ += this[RK].SZ );
return v;
},
})
//==========================================================================
// JSON HOOK
//==========================================================================
[PRIVATE]
({
JSON : function(/*obj*/x)
// ---------------------------------
// (JSON-Hook.) x being *probably* a ByteStream instance (as
// `x.constructor.name` is "ByteStream"), returns a string that
// evaluates to x, or '' if additional verification fails.
// => str [OK] | '' [KO]
{
if( (!x) || x.constructor !== callee.µ ) return ''; // [KO]
return __( "(new %1(%2))"
, callee.µ.name
, x.hasOwnProperty('__src__') ? x.__src__.toSource() : ''
);
},
})
//==========================================================================
// STATIC API
//==========================================================================
[STATIC]
({
onEngine: function onEngine_( $$,I)
//----------------------------------
// Globalize and install JSON hook.
{
$.global.ByteStream = callee.µ; // globalize me!
$$ = $.global[callee.µ.__root__];
I = callee.µ['~'];
$$.JSON.registerHook('ByteStream', I.JSON);
},
isTag: function isTag_S_B(/*str*/tag)
//----------------------------------
// Whether the string `tag` (uppercase) is a supported encoding tag.
// [REM250302] For the time being, all encoding tags have 3 characters,
// e.g. 'STR', 'HEX', 'I08', 'U24', 'FXP', 'F64' etc
// => bool
{
return 'function' == typeof(callee.µ['~']['W'+tag]);
},
isFormat: function isFormat_S_B(/*str*/fmt, I)
//----------------------------------
// Whether the string `fmt` is a valid encoding format (space separated
// sequence of encoding schemes.)
// => bool
{
I = callee.µ['~'];
fmt = fmt.split(I.SPCS).join(' ');
return fmt == (fmt.match(I.ENCS)||[]).join(' ');
},
sizeOf: function sizeOf_S_I(/*str*/fmt, I,re,a,z,i,m,x)
//----------------------------------
// Number of bytes consumed by the encoding format `fmt`
// -1 is returned if fmt is invalid.
// => uint [OK] | -1 [KO]
{
I = callee.µ['~'];
re = I.ENCS;
fmt = fmt.split(I.SPCS).join(' ');
a = fmt.match(re)||[];
if( fmt != a.join(' ') ) return -1;
for( z=0, i=a.length ; i-- ; z += x*I['R'+m[1]].SZ )
{
re.lastIndex = 0;
m = re.exec(a[i]); // m[1]:tag ; m[3]:''|'*nn'
x = (x=m[3]) ? parseInt(x.slice(1),10) : 1;
}