44
55use Bolt \packstream \Bytes ;
66use Bolt \protocol \IStructure ;
7+ use Bolt \protocol \v6 \structures \TypeMarker ;
78
89/**
910 * Class Vector
@@ -19,116 +20,133 @@ class Vector implements IStructure
1920 public function __construct (
2021 public readonly Bytes $ type_marker ,
2122 public readonly Bytes $ data
22- ) {
23- }
23+ ) {}
2424
2525 public function __toString (): string
2626 {
2727 return json_encode ([(string )$ this ->type_marker , (string )$ this ->data ]);
2828 }
2929
30- private static array $ formats = ['s ' , 'l ' , 'q ' ];
30+ private static array $ endiannessFormats = ['s ' , 'l ' , 'q ' ];
3131
3232 /**
3333 * Encode array as vector structure
34+ * This is a helper method to create Vector structure from array of numbers
3435 * @param int[]|float[] $data
36+ * @param TypeMarker|null $type Optional type to force specific data type .. null = auto decide
3537 * @return self
3638 * @throws \InvalidArgumentException
3739 */
38- public static function encode (array $ data ): self
40+ public static function encode (array $ data, ? TypeMarker $ type = null ): self
3941 {
40- if (count ($ data ) === 0 ) {
41- throw new \InvalidArgumentException ('Vector cannot be empty ' );
42+ $ anyFloat = false ;
43+ foreach ($ data as $ entry ) {
44+ if (!is_int ($ entry ) && !is_float ($ entry )) {
45+ throw new \InvalidArgumentException ('Vector can only contain numeric values ' );
46+ }
47+ if (!$ anyFloat && is_float ($ entry )) {
48+ $ anyFloat = true ;
49+ }
4250 }
43- if (count ($ data ) > 4096 ) {
44- throw new \InvalidArgumentException ('Vector cannot have more than 4096 elements ' );
51+
52+ if ($ type === null ) {
53+ $ type = self ::detectTypeMarker ($ anyFloat , count ($ data ) ? min ($ data ) : 0 , count ($ data ) ? max ($ data ) : 0 );
4554 }
4655
47- $ anyFloat = in_array (true , array_map ('is_float ' , $ data ));
48- $ minValue = min ($ data );
49- $ maxValue = max ($ data );
50- $ marker = 0 ;
5156 $ packFormat = '' ;
52-
53- if ($ anyFloat ) {
54- if ($ minValue >= 1.4e-45 && $ maxValue <= 3.4028235e+38 ) { // Single precision float (FLOAT_32)
55- $ marker = 0xC6 ;
57+ switch ($ type ) {
58+ case TypeMarker::FLOAT_32 :
5659 $ packFormat = 'G ' ;
57- } else { // Double precision float (FLOAT_64)
58- $ marker = 0xC1 ;
60+ break ;
61+ case TypeMarker:: FLOAT_64 :
5962 $ packFormat = 'E ' ;
60- }
61- } else {
62- if ($ minValue >= -128 && $ maxValue <= 127 ) { // INT_8
63- $ marker = 0xC8 ;
63+ break ;
64+ case TypeMarker::INT_8 :
6465 $ packFormat = 'c ' ;
65- } elseif ( $ minValue >= - 32768 && $ maxValue <= 32767 ) { // INT_16
66- $ marker = 0xC9 ;
66+ break ;
67+ case TypeMarker:: INT_16 :
6768 $ packFormat = 's ' ;
68- } elseif ( $ minValue >= - 2147483648 && $ maxValue <= 2147483647 ) { // INT_32
69- $ marker = 0xCA ;
69+ break ;
70+ case TypeMarker:: INT_32 :
7071 $ packFormat = 'l ' ;
71- } else { // INT_64
72- $ marker = 0xCB ;
72+ break ;
73+ case TypeMarker:: INT_64 :
7374 $ packFormat = 'q ' ;
74- }
75- }
76-
77- if ($ marker === 0 ) {
78- throw new \InvalidArgumentException ('Unsupported data type for vector ' );
75+ break ;
7976 }
8077
8178 // Pack the data
8279 $ packed = [];
8380 $ littleEndian = unpack ('S ' , "\x01\x00" )[1 ] === 1 ;
8481 foreach ($ data as $ entry ) {
85- $ value = pack ($ packFormat , $ entry );
86- $ packed [] = in_array ($ packFormat , self ::$ formats ) && $ littleEndian ? strrev ($ value ) : $ value ;
82+ $ value = pack ($ packFormat , $ anyFloat ? ( float ) $ entry : ( int ) $ entry );
83+ $ packed [] = in_array ($ packFormat , self ::$ endiannessFormats ) && $ littleEndian ? strrev ($ value ) : $ value ;
8784 }
8885
89- return new self (new Bytes ([chr ($ marker )]), new Bytes ($ packed ));
86+ return new self (new Bytes ([chr ($ type ->value )]), new Bytes ($ packed ));
87+ }
88+
89+ private static function detectTypeMarker (bool $ anyFloat , int |float $ minValue , int |float $ maxValue ): TypeMarker
90+ {
91+ if ($ anyFloat ) {
92+ if ($ minValue >= -3.4028235e+38 && $ maxValue <= 3.4028235e+38 ) { // Single precision float (FLOAT_32)
93+ return TypeMarker::FLOAT_32 ;
94+ } else { // Double precision float (FLOAT_64)
95+ return TypeMarker::FLOAT_64 ;
96+ }
97+ } else {
98+ if ($ minValue >= -128 && $ maxValue <= 127 ) { // INT_8
99+ return TypeMarker::INT_8 ;
100+ } elseif ($ minValue >= -32768 && $ maxValue <= 32767 ) { // INT_16
101+ return TypeMarker::INT_16 ;
102+ } elseif ($ minValue >= -2147483648 && $ maxValue <= 2147483647 ) { // INT_32
103+ return TypeMarker::INT_32 ;
104+ } else { // INT_64
105+ return TypeMarker::INT_64 ;
106+ }
107+ }
90108 }
91109
92110 /**
93- * Decode vector structure .. returns binary $this->data as array
111+ * Decode vector structure .. returns binary $this->data as array of numbers
94112 * @return int[]|float[]
95113 * @throws \InvalidArgumentException
96114 */
97115 public function decode (): array
98116 {
99117 switch (ord ($ this ->type_marker [0 ])) {
100- case 0xC8 : // INT_8
118+ case TypeMarker:: INT_8 -> value : // INT_8
101119 $ size = 1 ;
102120 $ unpackFormat = 'c ' ;
103121 break ;
104- case 0xC9 : // INT_16
122+ case TypeMarker:: INT_16 -> value : // INT_16
105123 $ size = 2 ;
106124 $ unpackFormat = 's ' ;
107125 break ;
108- case 0xCA : // INT_32
126+ case TypeMarker:: INT_32 -> value : // INT_32
109127 $ size = 4 ;
110128 $ unpackFormat = 'l ' ;
111129 break ;
112- case 0xCB : // INT_64
130+ case TypeMarker:: INT_64 -> value : // INT_64
113131 $ size = 8 ;
114132 $ unpackFormat = 'q ' ;
115133 break ;
116- case 0xC6 : // FLOAT_32
134+ case TypeMarker:: FLOAT_32 -> value : // FLOAT_32
117135 $ size = 4 ;
118136 $ unpackFormat = 'G ' ;
119137 break ;
120- case 0xC1 : // FLOAT_64
138+ case TypeMarker:: FLOAT_64 -> value : // FLOAT_64
121139 $ size = 8 ;
122140 $ unpackFormat = 'E ' ;
123141 break ;
124142 default :
125143 throw new \InvalidArgumentException ('Unknown vector type marker: ' . $ this ->type_marker [0 ]);
126144 }
127-
145+
128146 $ output = [];
129147 $ littleEndian = unpack ('S ' , "\x01\x00" )[1 ] === 1 ;
130- foreach (mb_str_split ((string )$ this ->data , $ size , '8bit ' ) as $ value ) {
131- $ output [] = unpack ($ unpackFormat , in_array ($ unpackFormat , self ::$ formats ) && $ littleEndian ? strrev ($ value ) : $ value )[1 ];
148+ foreach (mb_str_split ((string )$ this ->data , $ size , '8bit ' ) as $ value ) {
149+ $ output [] = unpack ($ unpackFormat , in_array ($ unpackFormat , self ::$ endiannessFormats ) && $ littleEndian ? strrev ($ value ) : $ value )[1 ];
132150 }
133151
134152 return $ output ;
0 commit comments