diff options
-rw-r--r-- | php/phpgats2.php | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/php/phpgats2.php b/php/phpgats2.php new file mode 100644 index 0000000..1e713fc --- /dev/null +++ b/php/phpgats2.php | |||
@@ -0,0 +1,471 @@ | |||
1 | <?php | ||
2 | |||
3 | /** @cond */ | ||
4 | if( !defined("_phpgats2_defined") ) | ||
5 | { | ||
6 | define("_phpgats2_defined",1); | ||
7 | /** @endcond */ | ||
8 | |||
9 | /** @mainpage phpgats.php PHP Gats Library | ||
10 | * @section Notes | ||
11 | * | ||
12 | * This library requires that php be built with the The GNU Multiple Precision Arithmetic Library (GMP). | ||
13 | * | ||
14 | * If array_values($a)===$a, the object will be encoded as a list, otherwise, it will be encoded as a dictionary. | ||
15 | * | ||
16 | * If an integer is > int32_max, it will be delivered as a string. | ||
17 | * | ||
18 | * If a string is encodable as an integer without loosing data, it will be. | ||
19 | * | ||
20 | * @section Usage | ||
21 | * | ||
22 | * function phpgats2_readGats( $str_data (string) ); | ||
23 | * returns mixed | ||
24 | * | ||
25 | * function phpgats2_writeGats( $elem (mixed) ); | ||
26 | * returns binary encoded gats string | ||
27 | * | ||
28 | */ | ||
29 | |||
30 | /** | ||
31 | * Call this function to generate a binary gats stream from the given php object | ||
32 | * @param $elem (mixed) the gats element object from which to generate a binary blob | ||
33 | * @returns (string) binary gats data | ||
34 | */ | ||
35 | function phpgats2_writeGats( $elem ) | ||
36 | { | ||
37 | $str_out = _phpgats2__write( $elem ); | ||
38 | $str_out = "\x01" . pack( "N", strlen($str_out)+5 ) . $str_out; | ||
39 | return $str_out; | ||
40 | } | ||
41 | |||
42 | /** | ||
43 | * Call this function to parse a gats binary string into a php type | ||
44 | * @param $str_data (string) the binary gats data to be parsed | ||
45 | * @returns (mixed) | ||
46 | */ | ||
47 | function phpgats2_readGats( $str_data ) | ||
48 | { | ||
49 | //print "parsing\n"; | ||
50 | $offset = 0; | ||
51 | $data_size = strlen( $str_data ); | ||
52 | if( $data_size < 5 ) | ||
53 | { | ||
54 | throw new Exception( "invalid size (< 5)\n" ); | ||
55 | return false; | ||
56 | } | ||
57 | if( ord($str_data) != 1 ) //version | ||
58 | { | ||
59 | throw new Exception( "invalid gats version" ); | ||
60 | return false; | ||
61 | } | ||
62 | $size = "" . $str_data[1] . $str_data[2] . $str_data[3] . $str_data[4]; | ||
63 | $size = unpack( "Nsize", $size ); | ||
64 | $size = $size["size"]; | ||
65 | if( $data_size < $size ) | ||
66 | { | ||
67 | throw new Exception( "Not enough data" ); | ||
68 | return false; | ||
69 | } | ||
70 | $offset+=5; | ||
71 | return _phpgats2_parseMaster( $str_data, $offset ); | ||
72 | } | ||
73 | |||
74 | /* --- STOP READING --- Below are internal functions you shouldn't call --- */ | ||
75 | |||
76 | /** | ||
77 | * Write a gats packed integer | ||
78 | * @param $iIn (either a string representing a number, a gmp_int, or an int) The integer to be converted | ||
79 | * @returns (string) packed int binary data | ||
80 | */ | ||
81 | //TODO::Make this use bcmath | ||
82 | function _phpgats2_packInt( $iIn ) | ||
83 | { | ||
84 | $ret = ""; | ||
85 | if( gmp_cmp( $iIn, 0 ) < 0 ) | ||
86 | { | ||
87 | $iIn = gmp_mul( $iIn, -1 ); | ||
88 | $b = gmp_intval( gmp_and( $iIn, 0x3f ) ); | ||
89 | if( gmp_cmp( $iIn, $b ) > 0 ) | ||
90 | $b |= 0x80 | 0x40; | ||
91 | else | ||
92 | $b |= 0x40; | ||
93 | } | ||
94 | else | ||
95 | { | ||
96 | $b = gmp_intval( gmp_and( $iIn, 0x3f ) ); | ||
97 | if( gmp_cmp( $iIn, $b ) > 0 ) | ||
98 | $b |= 0x80; | ||
99 | } | ||
100 | |||
101 | $ret .= chr( $b ); | ||
102 | $iIn = gmp_div( $iIn, 64 ); | ||
103 | |||
104 | while( gmp_cmp( $iIn, 0 ) > 0 ) | ||
105 | { | ||
106 | $b = gmp_intval( gmp_and( $iIn, 0x7f ) ); | ||
107 | if( gmp_cmp( $iIn, $b ) > 0 ) | ||
108 | $b |= 0x80; | ||
109 | $ret .= chr( $b ); | ||
110 | $iIn = gmp_div( $iIn, 128 ); | ||
111 | } | ||
112 | |||
113 | return $ret; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Read a gats packed integer into a gmp_int | ||
118 | * @param $sIn (string) the input stream | ||
119 | * @param &$pos (integer) the current position in the input stream (will be modified) | ||
120 | * @returns (gmp_int) the integer | ||
121 | */ | ||
122 | //TODO::Make this use bcmath | ||
123 | function _phpgats2_unpackInt( $sIn, &$pos ) | ||
124 | { | ||
125 | $neg = false; | ||
126 | |||
127 | $b = ord($sIn[$pos++]); | ||
128 | if( ($b&0x40) == 0x40 ) | ||
129 | $neg = true; | ||
130 | $iOut = gmp_init( $b&0x3f ); | ||
131 | $mult = gmp_init( 64 ); | ||
132 | while( ($b&0x80) ) | ||
133 | { | ||
134 | $b = ord($sIn[$pos++]); | ||
135 | $iOut = gmp_or( $iOut, gmp_mul( $b&0x7f, $mult ) ); | ||
136 | $mult = gmp_mul( $mult, 128 ); | ||
137 | } | ||
138 | if( $neg == true ) | ||
139 | $iOut = gmp_mul( $iOut, -1 ); | ||
140 | |||
141 | return $iOut; | ||
142 | } | ||
143 | |||
144 | function _phpgats2_writeBoolean( $b ) | ||
145 | { | ||
146 | return ($b==true)?"1":"0"; | ||
147 | } | ||
148 | |||
149 | function _phpgats2_writeString( $s ) | ||
150 | { | ||
151 | $s = (string)$s; | ||
152 | return 's' . _phpgats2_packInt(strlen($s)) . $s; | ||
153 | } | ||
154 | |||
155 | function _phpgats2_packInteger( $i ) | ||
156 | { | ||
157 | $i = (string)$i; | ||
158 | return "i" . _phpgats2_packInt(gmp_init(bcmul($i,'1',0))); | ||
159 | } | ||
160 | |||
161 | function _phpgats2_writeFloat( $f ) | ||
162 | { | ||
163 | $f = $f+0.0; | ||
164 | if( $f == 0.0 ) | ||
165 | { | ||
166 | return 'Fz'; | ||
167 | } | ||
168 | else if( is_nan( $f ) ) | ||
169 | { | ||
170 | return 'Fn'; | ||
171 | } | ||
172 | else if( is_infinite( $f ) ) | ||
173 | { | ||
174 | if( $f < 0.0 ) | ||
175 | return 'FI'; | ||
176 | else | ||
177 | return 'Fi'; | ||
178 | } | ||
179 | else | ||
180 | { | ||
181 | $e = $f; | ||
182 | $neg = false; | ||
183 | if( $e < 0.0 ) | ||
184 | { | ||
185 | $e = -$e; | ||
186 | $neg = true; | ||
187 | } | ||
188 | $iScale = (int)(floor(log($e)/log(256.0))); | ||
189 | $e = $e/pow(256.0, $iScale); | ||
190 | $s = chr((int)($e)); | ||
191 | $e = $e - (int)($e); | ||
192 | for( $j = 0; $j < 150 && $e > 0.0; $j++ ) | ||
193 | { | ||
194 | $e = $e * 256.0; | ||
195 | $s .= chr((int)($e)); | ||
196 | $e -= (int)($e); | ||
197 | } | ||
198 | $ilen = strlen($s); | ||
199 | if( $neg ) $ilen = -$ilen; | ||
200 | return "f" . _phpgats2_packInt($ilen) . $s . | ||
201 | _phpgats2_packInt($iScale); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | function _phpgats2_writeList( $l ) | ||
206 | { | ||
207 | $elems = array_values((array)$l); | ||
208 | |||
209 | $s_out = "l"; | ||
210 | foreach( $elems as $val ) | ||
211 | { | ||
212 | $s_out .= _phpgats2__write( $val ); | ||
213 | } | ||
214 | $s_out .= "e"; | ||
215 | return $s_out; | ||
216 | } | ||
217 | |||
218 | function _phpgats2_writeDictionary( $d ) | ||
219 | { | ||
220 | $elems = (array) $d; | ||
221 | $s_out = "d"; | ||
222 | foreach( $elems as $key => $val ) | ||
223 | { | ||
224 | $s_out .= _phpgats2_writeString( $key ); | ||
225 | $s_out .= _phpgats2__write( $val ); | ||
226 | } | ||
227 | $s_out .= "e"; | ||
228 | return $s_out; | ||
229 | } | ||
230 | |||
231 | function _phpgats2__write( $unknown ) | ||
232 | { | ||
233 | if( is_bool( $unknown ) ) | ||
234 | { | ||
235 | return _phpgats2_writeBoolean( $unknown ); | ||
236 | } | ||
237 | else if( is_float( $unknown ) ) | ||
238 | { | ||
239 | return _phpgats2_writeFloat( $unknown ); | ||
240 | } | ||
241 | else if( is_array( $unknown ) ) | ||
242 | { | ||
243 | if( array_values($unknown)===$unknown ) | ||
244 | return _phpgats2_writeList( $unknown ); | ||
245 | else | ||
246 | return _phpgats2_writeDictionary( $unknown ); | ||
247 | } | ||
248 | else if( is_object( $unknown ) ) | ||
249 | { | ||
250 | return _phpgats2_writeDictionary( $unknown ); | ||
251 | } | ||
252 | else if( is_int( $unknown ) ) | ||
253 | { | ||
254 | return _phpgats2_packInteger( $unknown ); | ||
255 | } | ||
256 | else if( is_string( $unknown ) ) | ||
257 | { | ||
258 | if( bcmul( $unknown, "1", 0 ) === $unknown ) | ||
259 | return _phpgats2_packInteger( $unknown ); | ||
260 | return _phpgats2_writeString( $unknown ); | ||
261 | } | ||
262 | } | ||
263 | |||
264 | /** | ||
265 | * Make sure we can read a character off the input stream | ||
266 | * @param $str_data (string) the input stream | ||
267 | * @param $offset (integer) the current position in the input stream | ||
268 | */ | ||
269 | function _phpgats2_parseChar( $str_data, $offset ) | ||
270 | { | ||
271 | if($offset>strlen($str_data)) | ||
272 | throw new Exception("Not enough data"); | ||
273 | } | ||
274 | |||
275 | /** | ||
276 | * Parse a string element out of the input stream | ||
277 | * @param $str_data (string) the input stream | ||
278 | * @param &$offset (integer) the current position in the input stream (will be updated) | ||
279 | * @param $dbg (integer) the current depth (for pretty printing) | ||
280 | * @returns (string) the element read | ||
281 | */ | ||
282 | function _phpgats2_parseString( $str_data, &$offset, $dbg ) | ||
283 | { | ||
284 | $str_tmp = ""; | ||
285 | $gmpSize = _phpgats2_unpackInt( $str_data, $offset ); | ||
286 | if( gmp_cmp( $gmpSize, 2147483647 ) > 0 ) | ||
287 | { | ||
288 | throw new Exception( | ||
289 | "size (" . gmp_strval($gmpSize) . ") > phpgats2 can handle\n"); | ||
290 | } | ||
291 | $iSize = gmp_intval($gmpSize); | ||
292 | $i=0; | ||
293 | $str_tmp = ""; | ||
294 | while( $i<$iSize ) | ||
295 | { | ||
296 | _phpgats2_parseChar( $str_data, $offset+1 ); | ||
297 | $str_tmp .= $str_data[$offset++]; | ||
298 | ++$i; | ||
299 | } | ||
300 | return $str_tmp; | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * Parse an integer element out of the input stream | ||
305 | * @param $str_data (string) the input stream | ||
306 | * @param &$offset (integer) the current position in the input stream (will be updated) | ||
307 | * @param $dbg (integer) the current depth (for pretty printing) | ||
308 | * @returns (integer or string) the element read | ||
309 | */ | ||
310 | function _phpgats2_parseInteger( $str_data, &$offset, $dbg ) | ||
311 | { | ||
312 | $gmp = _phpgats2_unpackInt($str_data, $offset); | ||
313 | $str = gmp_strval($gmp); | ||
314 | $int = gmp_intval($gmp); | ||
315 | if( $str === ((string)(int)$int) ) | ||
316 | return $int; | ||
317 | return $str; | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * Parse a float element out of the input stream | ||
322 | * @param $str_data (string) the input stream | ||
323 | * @param &$offset (integer) the current position in the input stream (will be updated) | ||
324 | * @param $dbg (integer) the current depth (for pretty printing) | ||
325 | * @returns (float) the element read | ||
326 | */ | ||
327 | function _phpgats2_parseFloat( $str_data, &$offset, $dbg ) | ||
328 | { | ||
329 | $str_tmp = ""; | ||
330 | $iSize = gmp_intval(_phpgats2_unpackInt( $str_data, $offset )); | ||
331 | $neg = false; | ||
332 | if( $iSize < 0.0 ) | ||
333 | { | ||
334 | $iSize = -$iSize; | ||
335 | $neg = true; | ||
336 | } | ||
337 | $i=0; | ||
338 | $str_tmp = ""; | ||
339 | while( $i<$iSize ) | ||
340 | { | ||
341 | _phpgats2_parseChar( $str_data, $offset+1 ); | ||
342 | $str_tmp .= $str_data[$offset++]; | ||
343 | ++$i; | ||
344 | } | ||
345 | $iScale = gmp_intval(_phpgats2_unpackInt( $str_data, $offset )); | ||
346 | $e = 0.0; | ||
347 | for( $j = $iSize-1; $j > 0; $j-- ) | ||
348 | { | ||
349 | $e = ($e+ord($str_tmp[$j]))*.00390625; | ||
350 | } | ||
351 | $e = ($e+ord($str_tmp[0])) * pow( 256.0, $iScale ); | ||
352 | if( $neg ) $e = -$e; | ||
353 | return $e; | ||
354 | } | ||
355 | |||
356 | /** | ||
357 | * Parse a list element out of the input stream | ||
358 | * @param $str_data (string) the input stream | ||
359 | * @param &$offset (integer) the current position in the input stream (will be updated) | ||
360 | * @param $dbg (integer) the current depth (for pretty printing) | ||
361 | * @returns (array) the element read | ||
362 | */ | ||
363 | function _phpgats2_parseList( $str_data, &$offset, $dbg ) | ||
364 | { | ||
365 | _phpgats2_parseChar( $str_data, $offset ); | ||
366 | $c = $str_data[$offset]; | ||
367 | $l_out = array(); | ||
368 | while( $c != "e" ) | ||
369 | { | ||
370 | $obj = _phpgats2_parseMaster( $str_data, $offset, $dbg ); | ||
371 | array_push( $l_out, $obj ); | ||
372 | _phpgats2_parseChar( $str_data, $offset ); | ||
373 | $c = $str_data[$offset]; | ||
374 | } | ||
375 | $offset++; | ||
376 | return $l_out; | ||
377 | } | ||
378 | |||
379 | /** | ||
380 | * Parse a dictionary element out of the input stream | ||
381 | * @param $str_data (string) the input stream | ||
382 | * @param &$offset (integer) the current position in the input stream (will be updated) | ||
383 | * @param $dbg (integer) the current depth (for pretty printing) | ||
384 | * @returns (array) the element read | ||
385 | */ | ||
386 | function _phpgats2_parseDictionary( $str_data, &$offset, $dbg ) | ||
387 | { | ||
388 | _phpgats2_parseChar( $str_data, $offset ); | ||
389 | $c = $str_data[$offset]; | ||
390 | $d_out = array(); | ||
391 | while( $c != "e" ) | ||
392 | { | ||
393 | $obj1 = _phpgats2_parseMaster( $str_data, $offset, $dbg ); | ||
394 | $obj2 = _phpgats2_parseMaster( $str_data, $offset, $dbg ); | ||
395 | $d_out[$obj1] = $obj2; | ||
396 | _phpgats2_parseChar( $str_data, $offset ); | ||
397 | $c = $str_data[$offset]; | ||
398 | } | ||
399 | $offset++; | ||
400 | return $d_out; | ||
401 | } | ||
402 | |||
403 | /** | ||
404 | * The internal master recursive parse function | ||
405 | * @param $str_data (string) the input stream | ||
406 | * @param &$offset (integer) the current position in the input stream (will be updated) | ||
407 | * @param $dbg (integer) the current depth (for pretty printing) | ||
408 | * @returns (mixed) the element read | ||
409 | */ | ||
410 | function _phpgats2_parseMaster( $str_data, &$offset, $dbg=0 ) | ||
411 | { | ||
412 | _phpgats2_parseChar( $str_data, $offset ); | ||
413 | $c = $str_data[$offset++]; | ||
414 | //for( $meme=0; $meme<$dbg; $meme++ ) | ||
415 | // echo " "; | ||
416 | switch( $c ) | ||
417 | { | ||
418 | case 'i': | ||
419 | //echo "int:"; | ||
420 | $obj = _phpgats2_parseInteger( $str_data, $offset, $dbg ); | ||
421 | //echo gmp_strval($obj->get()) . "\n"; | ||
422 | return $obj; | ||
423 | break; | ||
424 | case 'l': | ||
425 | //echo "list:\n"; | ||
426 | $obj = _phpgats2_parseList( $str_data, $offset, $dbg+1 ); | ||
427 | return $obj; | ||
428 | break; | ||
429 | case 'd': | ||
430 | //echo "dic:\n"; | ||
431 | $obj = _phpgats2_parseDictionary( $str_data, $offset, $dbg+1 ); | ||
432 | return $obj; | ||
433 | break; | ||
434 | case 'f': | ||
435 | //echo "float:\n"; | ||
436 | $obj = _phpgats2_parseFloat( $str_data, $offset, $dbg ); | ||
437 | return $obj; | ||
438 | break; | ||
439 | case 'F': | ||
440 | _phpgats2_parseChar( $str_data, $offset ); | ||
441 | switch( $str_data[$offset++] ) | ||
442 | { | ||
443 | case 'Z': return -0.0; | ||
444 | case 'z': return 0.0; | ||
445 | case 'N': return -NAN; | ||
446 | case 'n': return NAN; | ||
447 | case 'I': return -INF; | ||
448 | case 'i': return INF; | ||
449 | } | ||
450 | break; | ||
451 | case '1': | ||
452 | //echo "true\n"; | ||
453 | return true; | ||
454 | break; | ||
455 | case '0': | ||
456 | //echo "false\n"; | ||
457 | return false; | ||
458 | break; | ||
459 | default: | ||
460 | //echo "str:"; | ||
461 | $obj = _phpgats2_parseString( $str_data, $offset, $dbg ); | ||
462 | //echo $obj->get() . "\n"; | ||
463 | return $obj; | ||
464 | break; | ||
465 | } | ||
466 | } | ||
467 | |||
468 | /** @cond */ | ||
469 | } //defined("_phpgats2_defined"); | ||
470 | /** @endcond */ | ||
471 | |||