tinybits 0.8.0 → 0.9.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.
- checksums.yaml +4 -4
- data/ext/tinybits/tinybits.h +168 -42
- data/ext/tinybits/tinybits_ext.c +109 -44
- data/lib/tinybits/dpacker.rb +6 -48
- data/lib/tinybits/dunpacker.rb +5 -28
- data/lib/tinybits/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d36aff126ea60de67bc4050896a329f39e53a155cf50f4b12f35602757dc4ab1
|
|
4
|
+
data.tar.gz: 610909057ad8ae5f8fa0dab0e1dd5ee03105b7fb0036b11e3ac22eea97522cee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb4ce2802d01a5a62a119edc8f8c7ea06aff12edf0eff1d9b0d3a758d096e36c40c49c3ca52c22368da199b65b75900db1a5296580e38b9d2c00da62b80d6535
|
|
7
|
+
data.tar.gz: 16355ff8ae048bf3ae8d50eb2067cc42e01e21da1a396713d3a74e831e163658f3631dc19c09fc52c867fcf4b24bd29399029f44fabe9ac92be99eaccaa66a9b
|
data/ext/tinybits/tinybits.h
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TinyBits Amalgamated Header
|
|
3
|
-
* Generated on:
|
|
3
|
+
* Generated on: Tue 03 Nov 2026 03:43:24 AM CET
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#ifndef TINY_BITS_H
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
#include <math.h>
|
|
16
16
|
#include <stdio.h>
|
|
17
17
|
|
|
18
|
-
#define
|
|
18
|
+
#define TB_HASH_BITS 7
|
|
19
|
+
#define TB_HASH_SIZE (1 << TB_HASH_BITS)
|
|
19
20
|
#define TB_HASH_CACHE_SIZE 256
|
|
20
21
|
#define MAX_BYTES 9
|
|
21
22
|
#define TB_DDP_STR_LEN_MAX 128
|
|
@@ -84,7 +85,7 @@ typedef struct HashTable {
|
|
|
84
85
|
uint32_t next_id;
|
|
85
86
|
uint32_t cache_size;
|
|
86
87
|
uint32_t cache_pos;
|
|
87
|
-
|
|
88
|
+
uint16_t bins[TB_HASH_SIZE]; // 1-based cache index, must hold TB_HASH_CACHE_SIZE
|
|
88
89
|
} HashTable;
|
|
89
90
|
|
|
90
91
|
static inline uint32_t fast_hash_32(const char* str, uint16_t len) {
|
|
@@ -97,6 +98,11 @@ static inline uint32_t fast_hash_32(const char* str, uint16_t len) {
|
|
|
97
98
|
return hash;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
// bin index from the top bits of a multiplicative mix (the low bits alone are just the last byte)
|
|
102
|
+
static inline uint32_t tb_hash_bin(uint32_t hash_code) {
|
|
103
|
+
return (hash_code * 0x9E3779B1u) >> (32 - TB_HASH_BITS);
|
|
104
|
+
}
|
|
105
|
+
|
|
100
106
|
static inline int encode_varint(uint64_t value, uint8_t* buffer) {
|
|
101
107
|
if (value <= 240) {
|
|
102
108
|
buffer[0] = (uint8_t)value; // 1 byte
|
|
@@ -413,7 +419,9 @@ typedef struct tiny_bits_packer {
|
|
|
413
419
|
size_t capacity; // Total allocated size of the buffer
|
|
414
420
|
size_t current_pos; // Current position in the buffer (write position)
|
|
415
421
|
HashTable encode_table; // Add the hash table here
|
|
416
|
-
|
|
422
|
+
size_t dict_len; // dictionary bytes stored at the start of buffer
|
|
423
|
+
uint32_t dict_count; // dedupe cache entries taken by the dictionary
|
|
424
|
+
uint16_t dict_bins[TB_HASH_SIZE]; // encode_table.bins right after loading the dictionary
|
|
417
425
|
uint8_t features;
|
|
418
426
|
// Add any other encoder-specific state here if needed (e.g., string deduplication table later)
|
|
419
427
|
} tiny_bits_packer;
|
|
@@ -451,6 +459,9 @@ tiny_bits_packer *tiny_bits_packer_create(size_t initial_capacity, uint8_t featu
|
|
|
451
459
|
encoder->capacity = initial_capacity;
|
|
452
460
|
encoder->current_pos = 0;
|
|
453
461
|
encoder->features = features;
|
|
462
|
+
encoder->dict_len = 0;
|
|
463
|
+
encoder->dict_count = 0;
|
|
464
|
+
memset(encoder->dict_bins, 0, sizeof(encoder->dict_bins));
|
|
454
465
|
|
|
455
466
|
// Only allocate hash table if deduplication is enabled
|
|
456
467
|
if (features & TB_FEATURE_STRING_DEDUPE) {
|
|
@@ -464,7 +475,7 @@ tiny_bits_packer *tiny_bits_packer_create(size_t initial_capacity, uint8_t featu
|
|
|
464
475
|
encoder->encode_table.cache_size = TB_HASH_CACHE_SIZE;
|
|
465
476
|
encoder->encode_table.cache_pos = 0;
|
|
466
477
|
encoder->encode_table.next_id = 0;
|
|
467
|
-
memset(encoder->encode_table.bins, 0,
|
|
478
|
+
memset(encoder->encode_table.bins, 0, sizeof(encoder->encode_table.bins));
|
|
468
479
|
} else {
|
|
469
480
|
encoder->encode_table.cache = NULL;
|
|
470
481
|
encoder->encode_table.cache_size = 0;
|
|
@@ -482,15 +493,82 @@ tiny_bits_packer *tiny_bits_packer_create(size_t initial_capacity, uint8_t featu
|
|
|
482
493
|
*
|
|
483
494
|
* @note This function allows for more efficient packing by reusing the same packer object
|
|
484
495
|
*/
|
|
485
|
-
inline void tiny_bits_packer_reset(tiny_bits_packer *encoder) {
|
|
496
|
+
static inline void tiny_bits_packer_reset(tiny_bits_packer *encoder) {
|
|
486
497
|
if (!encoder) return;
|
|
487
|
-
encoder->current_pos =
|
|
498
|
+
encoder->current_pos = encoder->dict_len;
|
|
488
499
|
if (encoder->features & TB_FEATURE_STRING_DEDUPE) {
|
|
489
500
|
encoder->encode_table.next_id = 0;
|
|
490
|
-
encoder->encode_table.cache_pos =
|
|
491
|
-
|
|
501
|
+
encoder->encode_table.cache_pos = encoder->dict_count;
|
|
502
|
+
memcpy(encoder->encode_table.bins, encoder->dict_bins, sizeof(encoder->dict_bins));
|
|
492
503
|
}
|
|
493
|
-
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @brief Loads an external dictionary of strings used for deduplication
|
|
508
|
+
*
|
|
509
|
+
* @param encoder The packer instance (must have TB_FEATURE_STRING_DEDUPE)
|
|
510
|
+
* @param strs Array of string pointers (order is significant)
|
|
511
|
+
* @param lens Array of string lengths
|
|
512
|
+
* @param count Number of strings
|
|
513
|
+
* @return 1 on success, 0 on error
|
|
514
|
+
*
|
|
515
|
+
* @note The dictionary is not written to the output, the unpacker must be given the same dictionary.
|
|
516
|
+
* Strings are copied, and the packer is reset. Strings are assigned ids exactly as if they were packed
|
|
517
|
+
* (in order) at the start of every output, so only the first 256 dedupable strings (2-128 bytes) count.
|
|
518
|
+
*/
|
|
519
|
+
static inline int tiny_bits_packer_set_dict(tiny_bits_packer *encoder, const char **strs, const size_t *lens, size_t count) {
|
|
520
|
+
if (!encoder || !(encoder->features & TB_FEATURE_STRING_DEDUPE)) return 0;
|
|
521
|
+
HashTable *table = &encoder->encode_table;
|
|
522
|
+
encoder->dict_len = 0;
|
|
523
|
+
encoder->dict_count = 0;
|
|
524
|
+
memset(encoder->dict_bins, 0, sizeof(encoder->dict_bins));
|
|
525
|
+
tiny_bits_packer_reset(encoder);
|
|
526
|
+
|
|
527
|
+
for (size_t i = 0; i < count && table->cache_pos < TB_HASH_CACHE_SIZE; i++) {
|
|
528
|
+
const char *str = strs[i];
|
|
529
|
+
size_t len = lens[i];
|
|
530
|
+
if (len < 2 || len > 128) continue;
|
|
531
|
+
uint32_t hash_code = fast_hash_32(str, len);
|
|
532
|
+
uint32_t hash = tb_hash_bin(hash_code);
|
|
533
|
+
uint16_t index = table->bins[hash];
|
|
534
|
+
while (index > 0) {
|
|
535
|
+
HashEntry entry = table->cache[index - 1];
|
|
536
|
+
if (hash_code == entry.hash && len == entry.length
|
|
537
|
+
&& memcmp(str, encoder->buffer + entry.offset, len) == 0) break;
|
|
538
|
+
index = entry.next_index;
|
|
539
|
+
}
|
|
540
|
+
if (index > 0) continue; // duplicate, packing it would emit a ref and take no id
|
|
541
|
+
|
|
542
|
+
uint8_t *buffer = tiny_bits_packer_ensure_capacity(encoder, len);
|
|
543
|
+
if (!buffer) return 0;
|
|
544
|
+
memcpy(buffer, str, len);
|
|
545
|
+
HashEntry *new_entry = &table->cache[table->cache_pos++];
|
|
546
|
+
new_entry->hash = hash_code;
|
|
547
|
+
new_entry->length = len;
|
|
548
|
+
new_entry->offset = encoder->current_pos;
|
|
549
|
+
new_entry->next_index = table->bins[hash];
|
|
550
|
+
table->bins[hash] = table->cache_pos;
|
|
551
|
+
encoder->current_pos += len;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
encoder->dict_len = encoder->current_pos;
|
|
555
|
+
encoder->dict_count = table->cache_pos;
|
|
556
|
+
memcpy(encoder->dict_bins, table->bins, sizeof(encoder->dict_bins));
|
|
557
|
+
return 1;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* @brief Returns a pointer to the packed output (excludes the dictionary)
|
|
562
|
+
*/
|
|
563
|
+
static inline const unsigned char *tiny_bits_packer_data(const tiny_bits_packer *encoder) {
|
|
564
|
+
return encoder->buffer + encoder->dict_len;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* @brief Returns the size of the packed output (excludes the dictionary)
|
|
569
|
+
*/
|
|
570
|
+
static inline size_t tiny_bits_packer_size(const tiny_bits_packer *encoder) {
|
|
571
|
+
return encoder->current_pos - encoder->dict_len;
|
|
494
572
|
}
|
|
495
573
|
|
|
496
574
|
/**
|
|
@@ -715,8 +793,8 @@ static inline int pack_str(tiny_bits_packer *encoder, char* str, uint32_t str_le
|
|
|
715
793
|
uint32_t hash = 0;
|
|
716
794
|
if ((encoder->features & TB_FEATURE_STRING_DEDUPE) && str_len >= 2 && str_len <= 128) {
|
|
717
795
|
hash_code = fast_hash_32(str, str_len);
|
|
718
|
-
hash = hash_code
|
|
719
|
-
|
|
796
|
+
hash = tb_hash_bin(hash_code);
|
|
797
|
+
uint16_t index = encoder->encode_table.bins[hash];
|
|
720
798
|
while (index > 0) {
|
|
721
799
|
HashEntry entry = encoder->encode_table.cache[index - 1];
|
|
722
800
|
if (hash_code == entry.hash
|
|
@@ -940,7 +1018,8 @@ typedef struct tiny_bits_unpacker {
|
|
|
940
1018
|
} *strings; // Array of decoded strings
|
|
941
1019
|
size_t strings_size; // Capacity of strings array
|
|
942
1020
|
size_t strings_count; // Number of strings stored
|
|
943
|
-
|
|
1021
|
+
char *dict; // copy of the dictionary bytes, strings[0..dict_count) point into it
|
|
1022
|
+
size_t dict_count; // number of strings entries taken by the dictionary
|
|
944
1023
|
} tiny_bits_unpacker;
|
|
945
1024
|
|
|
946
1025
|
/**
|
|
@@ -962,9 +1041,63 @@ tiny_bits_unpacker *tiny_bits_unpacker_create(void) {
|
|
|
962
1041
|
return NULL;
|
|
963
1042
|
}
|
|
964
1043
|
decoder->strings_count = 0;
|
|
1044
|
+
decoder->dict = NULL;
|
|
1045
|
+
decoder->dict_count = 0;
|
|
965
1046
|
return decoder;
|
|
966
1047
|
}
|
|
967
1048
|
|
|
1049
|
+
/**
|
|
1050
|
+
* @brief Loads an external dictionary of strings, must match the one given to the packer
|
|
1051
|
+
*
|
|
1052
|
+
* @param decoder The unpacker instance
|
|
1053
|
+
* @param strs Array of string pointers (order is significant)
|
|
1054
|
+
* @param lens Array of string lengths
|
|
1055
|
+
* @param count Number of strings
|
|
1056
|
+
* @return 1 on success, 0 on error
|
|
1057
|
+
*
|
|
1058
|
+
* @note Strings are copied. Dictionary strings are returned as dedupe references, with ids 1..n
|
|
1059
|
+
* (see unpack_value), so client code can map them to pre-built objects.
|
|
1060
|
+
*/
|
|
1061
|
+
static inline int tiny_bits_unpacker_set_dict(tiny_bits_unpacker *decoder, const char **strs, const size_t *lens, size_t count) {
|
|
1062
|
+
if (!decoder) return 0;
|
|
1063
|
+
size_t total = 0;
|
|
1064
|
+
for (size_t i = 0; i < count; i++) total += lens[i];
|
|
1065
|
+
char *dict = (char *)malloc(total ? total : 1);
|
|
1066
|
+
if (!dict) return 0;
|
|
1067
|
+
free(decoder->dict);
|
|
1068
|
+
decoder->dict = dict;
|
|
1069
|
+
decoder->dict_count = 0;
|
|
1070
|
+
decoder->strings_count = 0;
|
|
1071
|
+
|
|
1072
|
+
size_t offset = 0;
|
|
1073
|
+
for (size_t i = 0; i < count && decoder->dict_count < TB_HASH_CACHE_SIZE; i++) {
|
|
1074
|
+
size_t len = lens[i];
|
|
1075
|
+
if (len < 2 || len > 128) continue;
|
|
1076
|
+
int duplicate = 0;
|
|
1077
|
+
for (size_t j = 0; j < decoder->dict_count; j++) {
|
|
1078
|
+
if (decoder->strings[j].length == len && memcmp(decoder->strings[j].str, strs[i], len) == 0) {
|
|
1079
|
+
duplicate = 1;
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (duplicate) continue;
|
|
1084
|
+
if (decoder->dict_count >= decoder->strings_size) {
|
|
1085
|
+
size_t new_size = decoder->strings_size * 2;
|
|
1086
|
+
void *new_strings = realloc(decoder->strings, new_size * sizeof(*decoder->strings));
|
|
1087
|
+
if (!new_strings) return 0;
|
|
1088
|
+
decoder->strings = new_strings;
|
|
1089
|
+
decoder->strings_size = new_size;
|
|
1090
|
+
}
|
|
1091
|
+
memcpy(dict + offset, strs[i], len);
|
|
1092
|
+
decoder->strings[decoder->dict_count].str = dict + offset;
|
|
1093
|
+
decoder->strings[decoder->dict_count].length = len;
|
|
1094
|
+
decoder->dict_count++;
|
|
1095
|
+
offset += len;
|
|
1096
|
+
}
|
|
1097
|
+
decoder->strings_count = decoder->dict_count;
|
|
1098
|
+
return 1;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
968
1101
|
/**
|
|
969
1102
|
* @breif Provides a buffer to the unpacker for unpacking
|
|
970
1103
|
*
|
|
@@ -982,7 +1115,7 @@ static inline void tiny_bits_unpacker_set_buffer(tiny_bits_unpacker *decoder, co
|
|
|
982
1115
|
decoder->buffer = buffer;
|
|
983
1116
|
decoder->size = size;
|
|
984
1117
|
decoder->current_pos = 0;
|
|
985
|
-
decoder->strings_count =
|
|
1118
|
+
decoder->strings_count = decoder->dict_count;
|
|
986
1119
|
}
|
|
987
1120
|
|
|
988
1121
|
/**
|
|
@@ -995,7 +1128,7 @@ static inline void tiny_bits_unpacker_set_buffer(tiny_bits_unpacker *decoder, co
|
|
|
995
1128
|
static inline void tiny_bits_unpacker_reset(tiny_bits_unpacker *decoder) {
|
|
996
1129
|
if (!decoder) return;
|
|
997
1130
|
decoder->current_pos = 0;
|
|
998
|
-
decoder->strings_count =
|
|
1131
|
+
decoder->strings_count = decoder->dict_count;
|
|
999
1132
|
}
|
|
1000
1133
|
|
|
1001
1134
|
|
|
@@ -1010,6 +1143,7 @@ void tiny_bits_unpacker_destroy(tiny_bits_unpacker *decoder) {
|
|
|
1010
1143
|
if (decoder->strings) {
|
|
1011
1144
|
free(decoder->strings);
|
|
1012
1145
|
}
|
|
1146
|
+
free(decoder->dict);
|
|
1013
1147
|
free(decoder);
|
|
1014
1148
|
}
|
|
1015
1149
|
|
|
@@ -1215,39 +1349,31 @@ static inline enum tiny_bits_type unpack_value(tiny_bits_unpacker *decoder, tiny
|
|
|
1215
1349
|
}
|
|
1216
1350
|
|
|
1217
1351
|
uint8_t tag = decoder->buffer[decoder->current_pos++];
|
|
1218
|
-
// Dispatch
|
|
1219
|
-
if (
|
|
1352
|
+
// Dispatch on tag ranges, each tag group starts at a power of two
|
|
1353
|
+
if (tag >= TB_INT_TAG) { // Integers
|
|
1220
1354
|
return _unpack_int(decoder, tag, value);
|
|
1221
|
-
} else if (
|
|
1355
|
+
} else if (tag >= TB_STR_TAG) { // Strings
|
|
1222
1356
|
return _unpack_str(decoder, tag, value);
|
|
1223
|
-
} else if (tag
|
|
1224
|
-
return
|
|
1225
|
-
|
|
1226
|
-
return
|
|
1227
|
-
} else if (tag == TB_INF_TAG) {
|
|
1228
|
-
return TINY_BITS_INF;
|
|
1229
|
-
} else if (tag == TB_NNF_TAG) {
|
|
1230
|
-
return TINY_BITS_N_INF;
|
|
1231
|
-
} else if ((tag & TB_DBL_TAG) == TB_DBL_TAG) { // Doubles
|
|
1357
|
+
} else if (tag >= TB_DBL_TAG) { // Doubles
|
|
1358
|
+
if (tag == TB_NAN_TAG) return TINY_BITS_NAN;
|
|
1359
|
+
if (tag == TB_INF_TAG) return TINY_BITS_INF;
|
|
1360
|
+
if (tag == TB_NNF_TAG) return TINY_BITS_N_INF;
|
|
1232
1361
|
return _unpack_double(decoder, tag, value);
|
|
1233
|
-
} else if (
|
|
1362
|
+
} else if (tag >= TB_MAP_TAG) { // Maps
|
|
1234
1363
|
return _unpack_map(decoder, tag, value);
|
|
1235
|
-
} else if (
|
|
1364
|
+
} else if (tag >= TB_ARR_TAG) { // Arrays
|
|
1236
1365
|
return _unpack_arr(decoder, tag, value);
|
|
1237
|
-
} else if (tag == TB_BLB_TAG) { // Blob
|
|
1238
|
-
return _unpack_blob(decoder, tag, value);
|
|
1239
|
-
} else if (tag == TB_DTM_TAG) {
|
|
1240
|
-
return _unpack_datetime(decoder, tag, value);
|
|
1241
|
-
} else if (tag == TB_SEP_TAG) {
|
|
1242
|
-
return TINY_BITS_SEP;
|
|
1243
|
-
} else if (tag == TB_EXT_TAG) {
|
|
1244
|
-
return TINY_BITS_EXT;
|
|
1245
|
-
} else if (tag == TB_TRU_TAG) {
|
|
1246
|
-
return TINY_BITS_TRUE;
|
|
1247
|
-
} else if (tag == TB_FLS_TAG) {
|
|
1248
|
-
return TINY_BITS_FALSE;
|
|
1249
1366
|
}
|
|
1250
|
-
|
|
1367
|
+
switch (tag) {
|
|
1368
|
+
case TB_NIL_TAG: return TINY_BITS_NULL;
|
|
1369
|
+
case TB_TRU_TAG: return TINY_BITS_TRUE;
|
|
1370
|
+
case TB_FLS_TAG: return TINY_BITS_FALSE;
|
|
1371
|
+
case TB_BLB_TAG: return _unpack_blob(decoder, tag, value);
|
|
1372
|
+
case TB_DTM_TAG: return _unpack_datetime(decoder, tag, value);
|
|
1373
|
+
case TB_SEP_TAG: return TINY_BITS_SEP;
|
|
1374
|
+
case TB_EXT_TAG: return TINY_BITS_EXT;
|
|
1375
|
+
}
|
|
1376
|
+
return TINY_BITS_ERROR; // Unknown tag (TB_NXT_TAG)
|
|
1251
1377
|
}
|
|
1252
1378
|
|
|
1253
1379
|
|
data/ext/tinybits/tinybits_ext.c
CHANGED
|
@@ -25,7 +25,7 @@ VALUE rb_cUnpacker;
|
|
|
25
25
|
|
|
26
26
|
// Forward declarations
|
|
27
27
|
static VALUE rb_packer_alloc(VALUE klass);
|
|
28
|
-
static VALUE rb_packer_init(VALUE self);
|
|
28
|
+
static VALUE rb_packer_init(int argc, VALUE* argv, VALUE self);
|
|
29
29
|
static VALUE rb_unpacker_alloc(VALUE klass);
|
|
30
30
|
static VALUE rb_unpacker_init(int argc, VALUE* argv, VALUE self);
|
|
31
31
|
static VALUE rb_pack(VALUE self, VALUE obj);
|
|
@@ -37,6 +37,8 @@ typedef struct {
|
|
|
37
37
|
int error_occurred; // 0 for no error, 1 if callback encountered an error
|
|
38
38
|
} HashIterContext;
|
|
39
39
|
|
|
40
|
+
static int binary_encindex; // rb_ascii8bit_encindex(), set in Init
|
|
41
|
+
|
|
40
42
|
// Forward declaration for the recursive function
|
|
41
43
|
static int pack_ruby_object_recursive(tiny_bits_packer* packer, VALUE obj, VALUE context);
|
|
42
44
|
|
|
@@ -71,7 +73,7 @@ static void packer_free(void* data) {
|
|
|
71
73
|
if (packer_data->packer) {
|
|
72
74
|
tiny_bits_packer_destroy(packer_data->packer);
|
|
73
75
|
}
|
|
74
|
-
|
|
76
|
+
xfree(packer_data);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
static size_t packer_memsize(const void* data) {
|
|
@@ -90,21 +92,52 @@ static VALUE rb_packer_alloc(VALUE klass) {
|
|
|
90
92
|
return TypedData_Wrap_Struct(klass, &packer_data_type, packer_data);
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
// Calls set_dict(target, strs, lens, count) with the #to_s of each dictionary member
|
|
96
|
+
#define WITH_DICT_STRINGS(dict, set_dict, target) do { \
|
|
97
|
+
Check_Type(dict, T_ARRAY); \
|
|
98
|
+
long dict_n = RARRAY_LEN(dict); \
|
|
99
|
+
VALUE dict_strs = rb_ary_new_capa(dict_n); \
|
|
100
|
+
for (long i = 0; i < dict_n; i++) { \
|
|
101
|
+
rb_ary_push(dict_strs, rb_obj_as_string(rb_ary_entry(dict, i))); \
|
|
102
|
+
} \
|
|
103
|
+
VALUE dict_buf; \
|
|
104
|
+
const char **dict_ptrs = ALLOCV(dict_buf, dict_n * (sizeof(char *) + sizeof(size_t)) + 1); \
|
|
105
|
+
size_t *dict_lens = (size_t *)(dict_ptrs + dict_n); \
|
|
106
|
+
for (long i = 0; i < dict_n; i++) { \
|
|
107
|
+
VALUE s = RARRAY_AREF(dict_strs, i); \
|
|
108
|
+
dict_ptrs[i] = RSTRING_PTR(s); \
|
|
109
|
+
dict_lens[i] = RSTRING_LEN(s); \
|
|
110
|
+
} \
|
|
111
|
+
int dict_ok = set_dict(target, dict_ptrs, dict_lens, dict_n); \
|
|
112
|
+
ALLOCV_END(dict_buf); \
|
|
113
|
+
RB_GC_GUARD(dict_strs); \
|
|
114
|
+
if (!dict_ok) rb_raise(rb_eRuntimeError, "Failed to set dictionary"); \
|
|
115
|
+
} while (0)
|
|
116
|
+
|
|
117
|
+
/*
|
|
94
118
|
* Document-method: initialize
|
|
95
119
|
*
|
|
96
120
|
* Initializes a new Packer object
|
|
97
121
|
*
|
|
122
|
+
* @param dict [Array] strings (or objects responding to #to_s) to deduplicate against without
|
|
123
|
+
* storing them in the output (order is significant), the unpacker must use the same dict
|
|
98
124
|
* @return [Packer] The initialized packer object.
|
|
99
125
|
*/
|
|
100
|
-
static VALUE rb_packer_init(VALUE self) {
|
|
126
|
+
static VALUE rb_packer_init(int argc, VALUE* argv, VALUE self) {
|
|
101
127
|
PackerData* packer_data;
|
|
102
128
|
TypedData_Get_Struct(self, PackerData, &packer_data_type, packer_data);
|
|
103
129
|
|
|
130
|
+
VALUE opts = Qnil;
|
|
131
|
+
rb_scan_args(argc, argv, ":", &opts);
|
|
132
|
+
VALUE dict = NIL_P(opts) ? Qnil : rb_hash_lookup(opts, ID2SYM(rb_intern("dict")));
|
|
133
|
+
|
|
104
134
|
packer_data->packer = tiny_bits_packer_create(256, (TB_FEATURE_STRING_DEDUPE | TB_FEATURE_COMPRESS_FLOATS) | 0); // Initial capacity and features
|
|
105
135
|
if (!packer_data->packer) {
|
|
106
136
|
rb_raise(rb_eRuntimeError, "Failed to initialize packer");
|
|
107
137
|
}
|
|
138
|
+
if (!NIL_P(dict)) {
|
|
139
|
+
WITH_DICT_STRINGS(dict, tiny_bits_packer_set_dict, packer_data->packer);
|
|
140
|
+
}
|
|
108
141
|
return self;
|
|
109
142
|
}
|
|
110
143
|
|
|
@@ -112,7 +145,7 @@ static VALUE rb_packer_init(VALUE self) {
|
|
|
112
145
|
static inline int pack_ruby_object_recursive(tiny_bits_packer* packer, VALUE obj, VALUE context) {
|
|
113
146
|
switch (TYPE(obj)) {
|
|
114
147
|
case T_STRING: {
|
|
115
|
-
if (ENCODING_GET_INLINED(obj) ==
|
|
148
|
+
if (ENCODING_GET_INLINED(obj) == binary_encindex)
|
|
116
149
|
return pack_blob(packer, RSTRING_PTR(obj), RSTRING_LEN(obj));
|
|
117
150
|
return pack_str(packer, RSTRING_PTR(obj), RSTRING_LEN(obj));
|
|
118
151
|
}
|
|
@@ -128,13 +161,12 @@ static inline int pack_ruby_object_recursive(tiny_bits_packer* packer, VALUE obj
|
|
|
128
161
|
int written = pack_arr(packer, len);
|
|
129
162
|
if (written <= 0) return 0; // Error check based on tiny_bits API
|
|
130
163
|
for (long i = 0; i < len; i++) {
|
|
131
|
-
if(!pack_ruby_object_recursive(packer,
|
|
164
|
+
if(!pack_ruby_object_recursive(packer, RARRAY_AREF(obj, i), context)) return 0; // Propagate error
|
|
132
165
|
}
|
|
133
166
|
return 1; // Success
|
|
134
167
|
}
|
|
135
168
|
case T_FIXNUM: {
|
|
136
|
-
|
|
137
|
-
return pack_int(packer, val);
|
|
169
|
+
return pack_int(packer, (int64_t)FIX2LONG(obj));
|
|
138
170
|
}
|
|
139
171
|
case T_BIGNUM: {
|
|
140
172
|
// within int64: a Bignum only past the Fixnum range (2**62)
|
|
@@ -145,8 +177,7 @@ static inline int pack_ruby_object_recursive(tiny_bits_packer* packer, VALUE obj
|
|
|
145
177
|
return pack_int(packer, (int64_t)NUM2LL(obj));
|
|
146
178
|
}
|
|
147
179
|
case T_FLOAT: {
|
|
148
|
-
|
|
149
|
-
return pack_double(packer, val);
|
|
180
|
+
return pack_double(packer, RFLOAT_VALUE(obj));
|
|
150
181
|
}
|
|
151
182
|
case T_NIL:
|
|
152
183
|
return pack_null(packer);
|
|
@@ -206,7 +237,7 @@ static VALUE rb_pack(VALUE self, VALUE obj) {
|
|
|
206
237
|
rb_raise(rb_eRuntimeError, "Failed to pack object (unsupported type or packing error)");
|
|
207
238
|
}
|
|
208
239
|
|
|
209
|
-
VALUE result = rb_str_new((const char*)packer_data->packer
|
|
240
|
+
VALUE result = rb_str_new((const char*)tiny_bits_packer_data(packer_data->packer), tiny_bits_packer_size(packer_data->packer));
|
|
210
241
|
rb_obj_freeze(result);
|
|
211
242
|
return result;
|
|
212
243
|
}
|
|
@@ -266,10 +297,10 @@ static VALUE rb_to_s(VALUE self){
|
|
|
266
297
|
rb_raise(rb_eRuntimeError, "Packer not initialized");
|
|
267
298
|
}
|
|
268
299
|
|
|
269
|
-
if(packer_data->packer
|
|
300
|
+
if(tiny_bits_packer_size(packer_data->packer) == 0){
|
|
270
301
|
return rb_str_new("", 0);
|
|
271
302
|
}
|
|
272
|
-
VALUE result = rb_str_new((const char*)packer_data->packer
|
|
303
|
+
VALUE result = rb_str_new((const char*)tiny_bits_packer_data(packer_data->packer), tiny_bits_packer_size(packer_data->packer));
|
|
273
304
|
rb_obj_freeze(result);
|
|
274
305
|
return result;
|
|
275
306
|
}
|
|
@@ -299,19 +330,27 @@ static VALUE rb_reset(VALUE self){
|
|
|
299
330
|
typedef struct {
|
|
300
331
|
tiny_bits_unpacker* unpacker;
|
|
301
332
|
int symbolize_keys; // map keys that are strings come back as symbols
|
|
302
|
-
//
|
|
303
|
-
|
|
333
|
+
VALUE strings; // Ruby strings by dedupe id, dictionary strings first
|
|
334
|
+
VALUE buffer; // the buffer set by #buffer=, kept alive while popping
|
|
304
335
|
} UnpackerData;
|
|
305
336
|
|
|
337
|
+
static void unpacker_mark(void* data) {
|
|
338
|
+
UnpackerData* unpacker_data = (UnpackerData*)data;
|
|
339
|
+
rb_gc_mark_movable(unpacker_data->strings);
|
|
340
|
+
rb_gc_mark(unpacker_data->buffer); // pinned, the C unpacker points into it (embedded strings move)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static void unpacker_compact(void* data) {
|
|
344
|
+
UnpackerData* unpacker_data = (UnpackerData*)data;
|
|
345
|
+
unpacker_data->strings = rb_gc_location(unpacker_data->strings);
|
|
346
|
+
}
|
|
347
|
+
|
|
306
348
|
static void unpacker_free(void* data) {
|
|
307
349
|
UnpackerData* unpacker_data = (UnpackerData*)data;
|
|
308
350
|
if (unpacker_data->unpacker) {
|
|
309
351
|
tiny_bits_unpacker_destroy(unpacker_data->unpacker);
|
|
310
352
|
}
|
|
311
|
-
|
|
312
|
-
//unpacker_data->ruby_strings[i] = Qnil;
|
|
313
|
-
//}
|
|
314
|
-
free(unpacker_data);
|
|
353
|
+
xfree(unpacker_data);
|
|
315
354
|
}
|
|
316
355
|
|
|
317
356
|
static size_t unpacker_memsize(const void* data) {
|
|
@@ -320,7 +359,7 @@ static size_t unpacker_memsize(const void* data) {
|
|
|
320
359
|
|
|
321
360
|
static const rb_data_type_t unpacker_data_type = {
|
|
322
361
|
"TinyBits::Unpacker",
|
|
323
|
-
{
|
|
362
|
+
{unpacker_mark, unpacker_free, unpacker_memsize, unpacker_compact,},
|
|
324
363
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
325
364
|
};
|
|
326
365
|
|
|
@@ -328,6 +367,8 @@ static VALUE rb_unpacker_alloc(VALUE klass) {
|
|
|
328
367
|
UnpackerData* unpacker_data = ALLOC(UnpackerData);
|
|
329
368
|
unpacker_data->unpacker = NULL;
|
|
330
369
|
unpacker_data->symbolize_keys = 0;
|
|
370
|
+
unpacker_data->strings = Qnil;
|
|
371
|
+
unpacker_data->buffer = Qnil;
|
|
331
372
|
return TypedData_Wrap_Struct(klass, &unpacker_data_type, unpacker_data);
|
|
332
373
|
}
|
|
333
374
|
|
|
@@ -337,6 +378,7 @@ static VALUE rb_unpacker_alloc(VALUE klass) {
|
|
|
337
378
|
* Initializes a new Unpacker.
|
|
338
379
|
*
|
|
339
380
|
* @param symbolize_keys [Boolean] unpack hash keys that are strings as symbols (default false)
|
|
381
|
+
* @param dict [Array] the dictionary the packer used (order is significant)
|
|
340
382
|
* @return [Unpacker] The initialized unpacker object.
|
|
341
383
|
*/
|
|
342
384
|
static VALUE rb_unpacker_init(int argc, VALUE* argv, VALUE self) {
|
|
@@ -344,9 +386,11 @@ static VALUE rb_unpacker_init(int argc, VALUE* argv, VALUE self) {
|
|
|
344
386
|
TypedData_Get_Struct(self, UnpackerData, &unpacker_data_type, unpacker_data);
|
|
345
387
|
|
|
346
388
|
VALUE opts = Qnil;
|
|
389
|
+
VALUE dict = Qnil;
|
|
347
390
|
rb_scan_args(argc, argv, ":", &opts);
|
|
348
391
|
if (!NIL_P(opts)) {
|
|
349
392
|
unpacker_data->symbolize_keys = RTEST(rb_hash_lookup(opts, ID2SYM(rb_intern("symbolize_keys"))));
|
|
393
|
+
dict = rb_hash_lookup(opts, ID2SYM(rb_intern("dict")));
|
|
350
394
|
}
|
|
351
395
|
|
|
352
396
|
unpacker_data->unpacker = tiny_bits_unpacker_create();
|
|
@@ -354,10 +398,22 @@ static VALUE rb_unpacker_init(int argc, VALUE* argv, VALUE self) {
|
|
|
354
398
|
rb_raise(rb_eRuntimeError, "Failed to initialize unpacker");
|
|
355
399
|
}
|
|
356
400
|
VALUE strings = rb_ary_new_capa(TB_HASH_CACHE_SIZE);
|
|
357
|
-
|
|
401
|
+
if (!NIL_P(dict)) {
|
|
402
|
+
tiny_bits_unpacker* unpacker = unpacker_data->unpacker;
|
|
403
|
+
WITH_DICT_STRINGS(dict, tiny_bits_unpacker_set_dict, unpacker);
|
|
404
|
+
// dictionary strings stay in the first dict_count slots, only later ones are cleared
|
|
405
|
+
for (size_t i = 0; i < unpacker->dict_count; i++) {
|
|
406
|
+
rb_ary_push(strings, rb_enc_interned_str(unpacker->strings[i].str, unpacker->strings[i].length, rb_utf8_encoding()));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
RB_OBJ_WRITE(self, &unpacker_data->strings, strings);
|
|
358
410
|
return self;
|
|
359
411
|
}
|
|
360
412
|
|
|
413
|
+
static inline void clear_strings(VALUE strings, UnpackerData* unpacker_data) {
|
|
414
|
+
rb_ary_resize(strings, unpacker_data->unpacker->dict_count);
|
|
415
|
+
}
|
|
416
|
+
|
|
361
417
|
static inline VALUE rb_unpack_str(VALUE strings, UnpackerData* unpacker_data, tiny_bits_value value, size_t interned){
|
|
362
418
|
int32_t id = value.str_blob_val.id;
|
|
363
419
|
if(id > 0)
|
|
@@ -418,14 +474,22 @@ static VALUE unpack_ruby_object(VALUE strings, UnpackerData* unpacker_data, size
|
|
|
418
474
|
}
|
|
419
475
|
case TINY_BITS_MAP: {
|
|
420
476
|
VALUE hash = rb_hash_new_capa(value.length);
|
|
421
|
-
|
|
422
|
-
|
|
477
|
+
VALUE pairs[2 * 16]; // small maps are inserted in one bulk call
|
|
478
|
+
int bulk = value.length <= 16;
|
|
479
|
+
for (size_t i = 0; i < value.length; i++) {
|
|
480
|
+
VALUE key = unpack_ruby_object(strings, unpacker_data, 1);
|
|
423
481
|
if (key == Qundef) return Qundef; // Error
|
|
424
482
|
if (unpacker_data->symbolize_keys && RB_TYPE_P(key, T_STRING)) key = rb_str_intern(key);
|
|
425
483
|
VALUE val = unpack_ruby_object(strings, unpacker_data, 0);
|
|
426
484
|
if (val == Qundef) return Qundef; // Error
|
|
427
|
-
|
|
485
|
+
if (bulk) {
|
|
486
|
+
pairs[2 * i] = key;
|
|
487
|
+
pairs[2 * i + 1] = val;
|
|
488
|
+
} else {
|
|
489
|
+
rb_hash_aset(hash, key, val);
|
|
490
|
+
}
|
|
428
491
|
}
|
|
492
|
+
if (bulk) rb_hash_bulk_insert(2 * value.length, pairs, hash);
|
|
429
493
|
return hash;
|
|
430
494
|
}
|
|
431
495
|
case TINY_BITS_BLOB:
|
|
@@ -462,14 +526,18 @@ static VALUE rb_unpack(VALUE self, VALUE buffer) {
|
|
|
462
526
|
|
|
463
527
|
tiny_bits_unpacker_set_buffer(unpacker_data->unpacker, (const unsigned char*)RSTRING_PTR(buffer), RSTRING_LEN(buffer));
|
|
464
528
|
|
|
465
|
-
VALUE array =
|
|
529
|
+
VALUE array = unpacker_data->strings;
|
|
530
|
+
clear_strings(array, unpacker_data); // a failed unpack or a pop session may have left entries
|
|
466
531
|
VALUE result = unpack_ruby_object(array, unpacker_data, 0);
|
|
532
|
+
RB_GC_GUARD(buffer);
|
|
533
|
+
// unpack is for one object only, a later #pop finds an empty buffer (and never reads this one)
|
|
534
|
+
unpacker_data->unpacker->current_pos = unpacker_data->unpacker->size;
|
|
467
535
|
|
|
468
536
|
if (result == Qundef) {
|
|
469
537
|
rb_raise(rb_eRuntimeError, "Failed to unpack data");
|
|
470
538
|
}
|
|
471
539
|
|
|
472
|
-
|
|
540
|
+
clear_strings(array, unpacker_data);
|
|
473
541
|
|
|
474
542
|
return result;
|
|
475
543
|
}
|
|
@@ -492,13 +560,13 @@ static VALUE rb_set_buffer(VALUE self, VALUE buffer){
|
|
|
492
560
|
}
|
|
493
561
|
|
|
494
562
|
StringValue(buffer); // Ensure it's a string
|
|
563
|
+
buffer = rb_str_new_frozen(buffer); // the caller mutating their string must not move the bytes we point into
|
|
495
564
|
|
|
496
565
|
tiny_bits_unpacker_set_buffer(unpacker_data->unpacker, (const unsigned char*)RSTRING_PTR(buffer), RSTRING_LEN(buffer));
|
|
497
566
|
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
rb_ary_clear(array);
|
|
567
|
+
// keep a reference to the buffer while popping
|
|
568
|
+
RB_OBJ_WRITE(self, &unpacker_data->buffer, buffer);
|
|
569
|
+
clear_strings(unpacker_data->strings, unpacker_data);
|
|
502
570
|
return self;
|
|
503
571
|
}
|
|
504
572
|
|
|
@@ -512,17 +580,14 @@ static VALUE rb_set_buffer(VALUE self, VALUE buffer){
|
|
|
512
580
|
* @note Requires #buffer= to be called first.
|
|
513
581
|
*/
|
|
514
582
|
static VALUE rb_pop(VALUE self) {
|
|
583
|
+
UnpackerData* unpacker_data;
|
|
584
|
+
TypedData_Get_Struct(self, UnpackerData, &unpacker_data_type, unpacker_data);
|
|
515
585
|
|
|
516
|
-
VALUE
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if(buffer == Qnil){
|
|
586
|
+
VALUE array = unpacker_data->strings;
|
|
587
|
+
if(unpacker_data->buffer == Qnil){
|
|
520
588
|
rb_raise(rb_eRuntimeError, "No buffer is set");
|
|
521
589
|
}
|
|
522
590
|
|
|
523
|
-
UnpackerData* unpacker_data;
|
|
524
|
-
TypedData_Get_Struct(self, UnpackerData, &unpacker_data_type, unpacker_data);
|
|
525
|
-
|
|
526
591
|
if (!unpacker_data->unpacker) {
|
|
527
592
|
rb_raise(rb_eRuntimeError, "Unpacker not initialized");
|
|
528
593
|
}
|
|
@@ -530,7 +595,7 @@ static VALUE rb_pop(VALUE self) {
|
|
|
530
595
|
tiny_bits_unpacker* unpacker = unpacker_data->unpacker;
|
|
531
596
|
|
|
532
597
|
if(unpacker->current_pos >= unpacker->size){
|
|
533
|
-
|
|
598
|
+
clear_strings(array, unpacker_data);
|
|
534
599
|
return Qnil;
|
|
535
600
|
}
|
|
536
601
|
|
|
@@ -540,7 +605,7 @@ static VALUE rb_pop(VALUE self) {
|
|
|
540
605
|
}
|
|
541
606
|
|
|
542
607
|
if(unpacker->current_pos >= unpacker->size){
|
|
543
|
-
|
|
608
|
+
clear_strings(array, unpacker_data);
|
|
544
609
|
}
|
|
545
610
|
|
|
546
611
|
return result;
|
|
@@ -556,12 +621,11 @@ static VALUE rb_pop(VALUE self) {
|
|
|
556
621
|
* @note Requires #buffer= to be called first.
|
|
557
622
|
*/
|
|
558
623
|
static VALUE rb_finished(VALUE self){
|
|
559
|
-
VALUE buffer = rb_iv_get(self, "@buffer");
|
|
560
|
-
if(buffer == Qnil){
|
|
561
|
-
rb_raise(rb_eRuntimeError, "No buffer is set");
|
|
562
|
-
}
|
|
563
624
|
UnpackerData* unpacker_data;
|
|
564
625
|
TypedData_Get_Struct(self, UnpackerData, &unpacker_data_type, unpacker_data);
|
|
626
|
+
if(unpacker_data->buffer == Qnil){
|
|
627
|
+
rb_raise(rb_eRuntimeError, "No buffer is set");
|
|
628
|
+
}
|
|
565
629
|
|
|
566
630
|
if (!unpacker_data->unpacker) {
|
|
567
631
|
rb_raise(rb_eRuntimeError, "Unpacker not initialized");
|
|
@@ -577,12 +641,13 @@ static VALUE rb_finished(VALUE self){
|
|
|
577
641
|
}
|
|
578
642
|
|
|
579
643
|
void Init_tinybits_ext(void) {
|
|
644
|
+
binary_encindex = rb_ascii8bit_encindex();
|
|
580
645
|
rb_mTinyBits = rb_define_module("TinyBits");
|
|
581
646
|
rb_cPacker = rb_define_class_under(rb_mTinyBits, "Packer", rb_cObject);
|
|
582
647
|
rb_cUnpacker = rb_define_class_under(rb_mTinyBits, "Unpacker", rb_cObject);
|
|
583
648
|
|
|
584
649
|
rb_define_alloc_func(rb_cPacker, rb_packer_alloc);
|
|
585
|
-
rb_define_method(rb_cPacker, "initialize", rb_packer_init,
|
|
650
|
+
rb_define_method(rb_cPacker, "initialize", rb_packer_init, -1);
|
|
586
651
|
rb_define_method(rb_cPacker, "pack", rb_pack, 1);
|
|
587
652
|
rb_define_alias(rb_cPacker, "encode", "pack");
|
|
588
653
|
rb_define_alias(rb_cPacker, "dump", "pack");
|
data/lib/tinybits/dpacker.rb
CHANGED
|
@@ -1,57 +1,15 @@
|
|
|
1
1
|
module TinyBits
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
|
|
3
|
+
# a Packer with an external dictionary, same as Packer.new(dict: dict)
|
|
4
|
+
class DPacker < Packer
|
|
5
|
+
|
|
5
6
|
# creates a new TinyBits::DPacker object
|
|
6
7
|
# @param dict [[String]] an array of strings to use as a dictiontary (order is significant).
|
|
7
8
|
# @return [TinyBits::DPacker] the dpacker object.
|
|
8
9
|
# @raise [RuntimeError] the dict is nil or if any member doesn't respnd to to_s.
|
|
9
10
|
def initialize(dict)
|
|
10
|
-
|
|
11
|
-
@dict_set = false
|
|
12
|
-
@packer = Packer.new
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def reset
|
|
16
|
-
@packer.reset
|
|
17
|
-
@dict_set = false
|
|
18
|
-
self
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# packs an object to a binary string using the stored dictionary
|
|
22
|
-
# @param obj [Object] The Ruby object to pack.
|
|
23
|
-
# @return [String] The packed binary string (frozen).
|
|
24
|
-
# @raise [RuntimeError] If packing fails due to unsupported types or other errors.
|
|
25
|
-
def pack(object)
|
|
26
|
-
reset
|
|
27
|
-
self << object
|
|
28
|
-
to_s
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# push an object to an inceremental buffer
|
|
32
|
-
# @param obj [Object] The Ruby object to pack.
|
|
33
|
-
# @return [Integer] The number of bytes written to the buffer.
|
|
34
|
-
# @raise [RuntimeError] If packing fails due to unsupported types or other errors.
|
|
35
|
-
def <<(object)
|
|
36
|
-
if !@dict_set
|
|
37
|
-
#@packer.reset
|
|
38
|
-
@dict_size = @packer << @dict
|
|
39
|
-
@dict_set = true
|
|
40
|
-
end
|
|
41
|
-
res = @packer << object
|
|
42
|
-
res
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# return the packed buffer
|
|
46
|
-
# @return [String] The packed buffer (minus the dictionary).
|
|
47
|
-
def to_s
|
|
48
|
-
res = @packer.to_s
|
|
49
|
-
if @dict_set
|
|
50
|
-
res[@dict_size, res.bytesize - @dict_size]
|
|
51
|
-
else
|
|
52
|
-
res
|
|
53
|
-
end
|
|
11
|
+
super(dict: dict)
|
|
54
12
|
end
|
|
55
13
|
|
|
56
14
|
end
|
|
57
|
-
end
|
|
15
|
+
end
|
data/lib/tinybits/dunpacker.rb
CHANGED
|
@@ -1,37 +1,14 @@
|
|
|
1
1
|
module TinyBits
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# an Unpacker with an external dictionary, same as Unpacker.new(dict: dict)
|
|
4
|
+
class DUnpacker < Unpacker
|
|
5
|
+
|
|
5
6
|
# @param dict [[String]] the dictionary the packer used (order is significant).
|
|
6
7
|
# @param symbolize_keys [Boolean] unpack hash keys that are strings as symbols.
|
|
7
8
|
def initialize(dict, symbolize_keys: false)
|
|
8
|
-
|
|
9
|
-
@unpacker = Unpacker.new(symbolize_keys: symbolize_keys)
|
|
10
|
-
@dict_popped = false
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def unpack(buffer)
|
|
14
|
-
self.buffer = buffer
|
|
15
|
-
pop
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def buffer=(buffer)
|
|
19
|
-
@unpacker.buffer = "#{@dict}#{buffer}"
|
|
20
|
-
@dict_popped = false
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def pop
|
|
24
|
-
if !@dict_popped
|
|
25
|
-
@unpacker.pop
|
|
26
|
-
@dict_popped = true
|
|
27
|
-
end
|
|
28
|
-
@unpacker.pop
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def finished?
|
|
32
|
-
@unpacker.finished?
|
|
9
|
+
super(dict: dict, symbolize_keys: symbolize_keys)
|
|
33
10
|
end
|
|
34
11
|
|
|
35
12
|
end
|
|
36
13
|
|
|
37
|
-
end
|
|
14
|
+
end
|
data/lib/tinybits/version.rb
CHANGED