string_bits 0.2.1 → 0.3.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/README.md +6 -2
- data/ext/string_bits/string_bits.c +399 -265
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c21407d901f599aa0d33fe0bad85d39f4729da08c656cab3556fc087198394f2
|
|
4
|
+
data.tar.gz: e4c8912125bfd9f4a6fcfce0be0d78f528f0bb87369064cfc1c96b4d3f5b7ac9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d73669323a7e9cee148d96775e8d9269b2c3c990906ad62e03d20466f93f3f2b7cafb911778fed607f7bdc561eaae57ac35770910de97f893addceb68e122bb
|
|
7
|
+
data.tar.gz: 359fd91c4eb04d4338850de1e38e2162c0d308a2697f86d2ef8ddbf200656384f62e81aa5eabac2c44c5afc79bde0383bc5b672a4a16ad50ab44fa0bcb195323
|
data/README.md
CHANGED
|
@@ -4,7 +4,9 @@ Ruby's `String` is already a byte sequence. This gem extends it one level lower:
|
|
|
4
4
|
|
|
5
5
|
The methods are designed for real workloads: Apache Arrow validity bitmaps, bitmap font glyph data, and any protocol buffer where bits carry meaning. Many methods read or modify the existing string bytes directly; methods such as `bit_slice`, `bitwise_not`, `bitwise_and`, `bitwise_or`, `bitwise_xor`, `bits`, `bit_offsets`, `bit_runs`, and `Array#mask` allocate a derived result object. Because the API relies solely on `String`, the same methods would benefit memory-constrained implementations such as mruby and PicoRuby once adopted into CRuby.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Every method reads the receiver as a raw byte sequence, so the `String`s that `bit_slice` and the non-destructive `bitwise_*` methods return are always `Encoding::BINARY`. The destructive methods leave the receiver's encoding untouched, as `String#setbyte` does.
|
|
8
|
+
|
|
9
|
+
A single keyword, `lsb_first:`, controls bit ordering across the API: it selects intra-byte numbering wherever positions are exchanged with the caller, and intra-byte scan direction wherever the API walks the sequence. Across-byte order is always `byte[0]` first, and result Strings from `bit_slice` are always packed LSB-first regardless, so `data.each_bit_offset(true, lsb_first: false).all? { |n| data.bit_set?(n, lsb_first: false) }` stays true.
|
|
8
10
|
|
|
9
11
|
## Usage
|
|
10
12
|
|
|
@@ -14,7 +16,9 @@ gem install string_bits
|
|
|
14
16
|
|
|
15
17
|
```ruby
|
|
16
18
|
require "string_bits"
|
|
17
|
-
puts "\xAA\xAA\xAA\xAA".
|
|
19
|
+
puts "\xAA\xAA\xAA\xAA".bit_get(10)
|
|
20
|
+
#=> 0
|
|
21
|
+
puts "\xAA\xAA\xAA\xAA".bit_set?(10)
|
|
18
22
|
#=> false
|
|
19
23
|
|
|
20
24
|
puts "\xAA".each_bit(lsb_first: false).to_a.inspect
|
|
@@ -202,22 +202,179 @@ test_bit(const char *ptr, ssize_t bit_index)
|
|
|
202
202
|
return rb_str_get_bit(ptr, bit_index, 1);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
/*
|
|
205
|
+
/* Bit position arguments -------------------------------------------------- */
|
|
206
|
+
/*
|
|
207
|
+
* Every bit offset in this API -- the single-bit offset of bit_get and friends,
|
|
208
|
+
* the offset of an offset/length pair, and each endpoint of a bit Range --
|
|
209
|
+
* follows one contract, the one accepted into Ruby core:
|
|
210
|
+
*
|
|
211
|
+
* - the argument goes through rb_to_int, so any object responding to #to_int
|
|
212
|
+
* is accepted, exactly like the index of String#setbyte;
|
|
213
|
+
* - a negative offset raises IndexError. Negative positions are not read as
|
|
214
|
+
* count-from-end, because combining that normalization with the
|
|
215
|
+
* lsb_first: true/false numbering would make the addressing rule much
|
|
216
|
+
* harder to state (see Discussion.md);
|
|
217
|
+
* - any non-negative offset below 2**64 is a well-formed position. One past
|
|
218
|
+
* the end of the receiver is not an error in itself: it takes the ordinary
|
|
219
|
+
* out-of-range path of whichever method received it (nil, zero, an empty
|
|
220
|
+
* iteration, or IndexError for a mutation);
|
|
221
|
+
* - only an offset that does not fit in uint64_t raises ArgumentError, since
|
|
222
|
+
* no byte buffer on any platform can be addressed by it.
|
|
223
|
+
*
|
|
224
|
+
* The return type is therefore uint64_t. Callers bounds-check it against
|
|
225
|
+
* SB_BIT_LEN(), and any offset that survives that check is below the bit
|
|
226
|
+
* length of a real string, hence always representable as ssize_t.
|
|
206
227
|
*
|
|
207
|
-
* Raises ArgumentError for Bignums on all platforms: a Bignum cannot be a
|
|
208
|
-
* valid bit index for any real string, and raising explicitly is clearer than
|
|
209
|
-
* silently mapping to a sentinel value that later triggers a different error.
|
|
210
228
|
* NUM2SSIZET is width-aware (uses FIX2LL on LLP64, FIX2LONG on LP64) so the
|
|
211
229
|
* FIXNUM extraction does not truncate large FIXNUMs on Windows.
|
|
230
|
+
* RBIGNUM_NEGATIVE_P and rb_absint_size are available via ruby.h.
|
|
212
231
|
*
|
|
213
|
-
*
|
|
214
|
-
|
|
215
|
-
|
|
232
|
+
* Porting to Ruby Core:
|
|
233
|
+
* Core keeps a `long` fast path alongside the uint64_t value so that the
|
|
234
|
+
* common Fixnum case never leaves the pointer-width arithmetic. That split
|
|
235
|
+
* is a pure optimization; the observable contract is the one above.
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/* True when a positive Integer does not fit uint64_t. rb_absint_size returns
|
|
239
|
+
* the byte width of the absolute value, so the bound is exactly 8 bytes. */
|
|
240
|
+
static inline int
|
|
241
|
+
sb_exceeds_uint64(VALUE integer)
|
|
242
|
+
{
|
|
243
|
+
return rb_absint_size(integer, NULL) > sizeof(uint64_t);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static uint64_t
|
|
247
|
+
sb_bit_offset(VALUE n)
|
|
248
|
+
{
|
|
249
|
+
VALUE integer = rb_to_int(n);
|
|
250
|
+
|
|
251
|
+
if (FIXNUM_P(integer)) {
|
|
252
|
+
ssize_t value = NUM2SSIZET(integer);
|
|
253
|
+
if (value < 0) {
|
|
254
|
+
rb_raise(rb_eIndexError, "bit index out of range");
|
|
255
|
+
}
|
|
256
|
+
return (uint64_t)value;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
RUBY_ASSERT(RB_TYPE_P(integer, T_BIGNUM));
|
|
260
|
+
if (RBIGNUM_NEGATIVE_P(integer)) {
|
|
261
|
+
rb_raise(rb_eIndexError, "bit index out of range");
|
|
262
|
+
}
|
|
263
|
+
if (sb_exceeds_uint64(integer)) {
|
|
264
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
265
|
+
}
|
|
266
|
+
return (uint64_t)NUM2ULL(integer);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Bit lengths share the offset's representable range, but a negative length is
|
|
270
|
+
* an ArgumentError rather than an IndexError: it is a malformed size, not a
|
|
271
|
+
* position outside the string. A length that runs past the end of the receiver
|
|
272
|
+
* is clamped by the caller, matching byteslice. */
|
|
273
|
+
static uint64_t
|
|
274
|
+
sb_bit_length(VALUE n)
|
|
216
275
|
{
|
|
217
|
-
|
|
276
|
+
VALUE integer = rb_to_int(n);
|
|
277
|
+
|
|
278
|
+
if (FIXNUM_P(integer)) {
|
|
279
|
+
ssize_t value = NUM2SSIZET(integer);
|
|
280
|
+
if (value < 0) {
|
|
281
|
+
rb_raise(rb_eArgError, "bit_length must be non-negative");
|
|
282
|
+
}
|
|
283
|
+
return (uint64_t)value;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
RUBY_ASSERT(RB_TYPE_P(integer, T_BIGNUM));
|
|
287
|
+
if (RBIGNUM_NEGATIVE_P(integer)) {
|
|
288
|
+
rb_raise(rb_eArgError, "bit_length must be non-negative");
|
|
289
|
+
}
|
|
290
|
+
if (sb_exceeds_uint64(integer)) {
|
|
291
|
+
rb_raise(rb_eArgError, "bit_length out of representable range");
|
|
292
|
+
}
|
|
293
|
+
return (uint64_t)NUM2ULL(integer);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Soft variants for bit_slice, which answers nil for an argument it cannot
|
|
297
|
+
* use instead of raising -- the same leniency String#byteslice has. Only the
|
|
298
|
+
* unrepresentable case still raises, so the boundary between "a position past
|
|
299
|
+
* the end" and "not a position at all" stays where the rest of the API puts
|
|
300
|
+
* it. Returns 0 on success, or -1 for a non-Integer or a negative value. */
|
|
301
|
+
static int
|
|
302
|
+
sb_bit_position_soft(VALUE n, uint64_t *out)
|
|
303
|
+
{
|
|
304
|
+
if (!rb_integer_type_p(n)) return -1;
|
|
305
|
+
|
|
306
|
+
if (FIXNUM_P(n)) {
|
|
307
|
+
ssize_t value = NUM2SSIZET(n);
|
|
308
|
+
if (value < 0) return -1;
|
|
309
|
+
*out = (uint64_t)value;
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
218
313
|
RUBY_ASSERT(RB_TYPE_P(n, T_BIGNUM));
|
|
219
|
-
|
|
220
|
-
|
|
314
|
+
if (RBIGNUM_NEGATIVE_P(n)) return -1;
|
|
315
|
+
if (sb_exceeds_uint64(n)) {
|
|
316
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
317
|
+
}
|
|
318
|
+
*out = (uint64_t)NUM2ULL(n);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* Narrowing a parsed position to the internal index ----------------------- */
|
|
323
|
+
/*
|
|
324
|
+
* Positions are parsed in 64 bits but held in a pointer-width signed integer
|
|
325
|
+
* once they address memory, and on an ILP32 build those two widths differ:
|
|
326
|
+
* SB_BIT_LEN can reach 2**34 while ssize_t stops at 2**31-1, so a string of
|
|
327
|
+
* 256 MiB or more has bits that exist but cannot be indexed internally. Such a
|
|
328
|
+
* position gets the same answer as one that does not fit 64 bits at all --
|
|
329
|
+
* ArgumentError -- rather than being silently truncated into a valid-looking
|
|
330
|
+
* index. On LP64 the gap is empty and none of this is reachable: no String is
|
|
331
|
+
* large enough for its bit length to exceed SSIZE_MAX.
|
|
332
|
+
*/
|
|
333
|
+
#ifndef SSIZE_MAX
|
|
334
|
+
# define SSIZE_MAX ((ssize_t)(((size_t)-1) >> 1))
|
|
335
|
+
#endif
|
|
336
|
+
#define SB_MAX_BIT_POS ((uint64_t)SSIZE_MAX)
|
|
337
|
+
|
|
338
|
+
/* The receiver's bit length as an internal index, saturated at ssize_t. */
|
|
339
|
+
static inline ssize_t
|
|
340
|
+
sb_addressable_bits(int64_t total)
|
|
341
|
+
{
|
|
342
|
+
return (ssize_t)(((uint64_t)total > SB_MAX_BIT_POS) ? SB_MAX_BIT_POS : (uint64_t)total);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* Narrow a parsed position for the methods whose answer at or past the end is
|
|
346
|
+
* "there is nothing here" (an empty iteration, a count of zero, an empty
|
|
347
|
+
* slice): those saturate at the end of the string rather than failing. */
|
|
348
|
+
static ssize_t
|
|
349
|
+
sb_narrow_bit_pos(uint64_t pos, int64_t total)
|
|
350
|
+
{
|
|
351
|
+
if (pos >= (uint64_t)total) return sb_addressable_bits(total);
|
|
352
|
+
if (pos > SB_MAX_BIT_POS) {
|
|
353
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
354
|
+
}
|
|
355
|
+
return (ssize_t)pos;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Clamp a parsed length to the bits still addressable after `beg`. */
|
|
359
|
+
static ssize_t
|
|
360
|
+
sb_clamp_bit_length(uint64_t len, ssize_t beg, int64_t total)
|
|
361
|
+
{
|
|
362
|
+
uint64_t available = (uint64_t)sb_addressable_bits(total) - (uint64_t)beg;
|
|
363
|
+
return (ssize_t)(len > available ? available : len);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* Resolve a single-bit offset against the receiver's bit length.
|
|
367
|
+
* Returns -1 when the offset is past the end of the string. */
|
|
368
|
+
static ssize_t
|
|
369
|
+
sb_resolve_bit_offset(VALUE self, VALUE n)
|
|
370
|
+
{
|
|
371
|
+
int64_t total = SB_BIT_LEN(RSTRING_LEN(self));
|
|
372
|
+
uint64_t offset = sb_bit_offset(n);
|
|
373
|
+
if ((uint64_t)total <= offset) return -1;
|
|
374
|
+
if (offset > SB_MAX_BIT_POS) {
|
|
375
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
376
|
+
}
|
|
377
|
+
return (ssize_t)offset;
|
|
221
378
|
}
|
|
222
379
|
|
|
223
380
|
/* Bit numbering between byte-with-LSB-as-bit-0 and byte-with-MSB-as-bit-0
|
|
@@ -253,54 +410,32 @@ logical_write_bit(unsigned char *ptr, ssize_t logical_index, int lsb_first, int
|
|
|
253
410
|
static ssize_t
|
|
254
411
|
check_bit_index(VALUE self, VALUE n, int lsb_first)
|
|
255
412
|
{
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
ssize_t idx = integer_to_bit_idx(n);
|
|
260
|
-
int64_t size = SB_BIT_LEN(RSTRING_LEN(self));
|
|
261
|
-
if (idx < 0 || idx >= size) {
|
|
413
|
+
ssize_t idx = sb_resolve_bit_offset(self, n);
|
|
414
|
+
if (idx < 0) {
|
|
262
415
|
rb_raise(rb_eIndexError, "bit index out of range");
|
|
263
416
|
}
|
|
264
417
|
return logical_to_physical(idx, lsb_first);
|
|
265
418
|
}
|
|
266
419
|
|
|
267
|
-
/*
|
|
420
|
+
/* Resolve a bit Range against the receiver's bit length.
|
|
268
421
|
*
|
|
269
|
-
* rb_range_beg_len()
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
422
|
+
* This replaces rb_range_beg_len() rather than wrapping it, for two reasons.
|
|
423
|
+
* rb_range_beg_len() takes `long *` endpoints, so on Windows (LLP64), where
|
|
424
|
+
* long is 32-bit while ssize_t is 64-bit, its interface does not match the
|
|
425
|
+
* pointer-width bit index used here. More importantly, it raises RangeError
|
|
426
|
+
* for any endpoint that does not fit `long`, whereas a bit Range endpoint is
|
|
427
|
+
* allowed to be any position up to UINT64_MAX (see sb_bit_offset); such an
|
|
428
|
+
* endpoint simply lies past the end of the string.
|
|
276
429
|
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
int err;
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
static VALUE
|
|
290
|
-
sb_range_beg_len_call(VALUE arg)
|
|
291
|
-
{
|
|
292
|
-
struct sb_range_args *a = (struct sb_range_args *)arg;
|
|
293
|
-
return rb_range_beg_len(a->range, a->lbegp, a->llenp, a->len, a->err);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/* Validate Range endpoints for bit position arguments.
|
|
297
|
-
* Raises ArgumentError for:
|
|
298
|
-
* - any explicit (non-nil) Bignum endpoint: cannot address any real string,
|
|
299
|
-
* consistent with integer_to_bit_idx behavior for scalar indices.
|
|
300
|
-
* - any explicit (non-nil) negative endpoint: count-from-end semantics
|
|
301
|
-
* interact confusingly with lsb_first: true/false.
|
|
302
|
-
* RBIGNUM_NEGATIVE_P is used for the negativity check on Bignums to avoid
|
|
303
|
-
* calling NUM2LL on values that do not fit in long long.
|
|
430
|
+
* The resolution rules match rb_range_beg_len() with err=0, minus the
|
|
431
|
+
* count-from-end normalization that this API does not have:
|
|
432
|
+
* - a nil begin is 0, a nil end runs to the end of the string;
|
|
433
|
+
* - an end past the end of the string is clamped to it;
|
|
434
|
+
* - a begin past the end of the string reports SB_RANGE_OUT, and each caller
|
|
435
|
+
* decides what that means (0 bits counted, a nil slice, an IndexError).
|
|
436
|
+
* Like rb_range_beg_len(), the returned length may exceed the bits actually
|
|
437
|
+
* available when the end was clamped, so callers that write must re-check
|
|
438
|
+
* beg + len against the total (see rb_str_mutate_bits and rb_str_bit_splice).
|
|
304
439
|
*
|
|
305
440
|
* Porting to Ruby Core:
|
|
306
441
|
* Replace rb_range_values() with direct struct access:
|
|
@@ -309,47 +444,39 @@ sb_range_beg_len_call(VALUE arg)
|
|
|
309
444
|
* end = RANGE_END(range);
|
|
310
445
|
* excl = RANGE_EXCL(range);
|
|
311
446
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
447
|
+
enum sb_range_result {
|
|
448
|
+
SB_RANGE_OUT = 0, /* the range begins past the end of the string */
|
|
449
|
+
SB_RANGE_OK = 1
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
static enum sb_range_result
|
|
453
|
+
sb_bit_range_beg_len(VALUE range, ssize_t *begp, ssize_t *lenp, int64_t total)
|
|
314
454
|
{
|
|
315
|
-
VALUE
|
|
455
|
+
VALUE beg_v, end_v;
|
|
316
456
|
int excl;
|
|
317
|
-
rb_range_values(range, &
|
|
318
|
-
if (!NIL_P(beg) && rb_integer_type_p(beg)) {
|
|
319
|
-
if (!FIXNUM_P(beg))
|
|
320
|
-
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
321
|
-
if (FIX2LONG(beg) < 0)
|
|
322
|
-
rb_raise(rb_eIndexError,
|
|
323
|
-
"negative Range endpoint is not allowed for bit positions");
|
|
324
|
-
}
|
|
325
|
-
if (!NIL_P(end) && rb_integer_type_p(end)) {
|
|
326
|
-
if (!FIXNUM_P(end))
|
|
327
|
-
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
328
|
-
if (FIX2LONG(end) < 0)
|
|
329
|
-
rb_raise(rb_eIndexError,
|
|
330
|
-
"negative Range endpoint is not allowed for bit positions");
|
|
331
|
-
}
|
|
332
|
-
}
|
|
457
|
+
rb_range_values(range, &beg_v, &end_v, &excl);
|
|
333
458
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
VALUE result = rb_protect(sb_range_beg_len_call, (VALUE)&args, &state);
|
|
342
|
-
if (state) {
|
|
343
|
-
VALUE exc = rb_errinfo();
|
|
344
|
-
rb_set_errinfo(Qnil);
|
|
345
|
-
if (rb_obj_is_kind_of(exc, rb_eRangeError)) {
|
|
346
|
-
rb_raise(rb_eIndexError, "bit range out of range");
|
|
347
|
-
}
|
|
348
|
-
rb_exc_raise(exc);
|
|
459
|
+
uint64_t total_u = (uint64_t)total;
|
|
460
|
+
uint64_t beg = NIL_P(beg_v) ? 0 : sb_bit_offset(beg_v);
|
|
461
|
+
if (beg > total_u) return SB_RANGE_OUT;
|
|
462
|
+
|
|
463
|
+
uint64_t end_exclusive;
|
|
464
|
+
if (NIL_P(end_v)) {
|
|
465
|
+
end_exclusive = total_u;
|
|
349
466
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
467
|
+
else {
|
|
468
|
+
uint64_t end = sb_bit_offset(end_v);
|
|
469
|
+
if (end > total_u) end = total_u;
|
|
470
|
+
end_exclusive = excl ? end : end + 1;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* The length is deliberately left unclamped: when the end was clamped it
|
|
474
|
+
* can exceed the bits available, which is exactly what lets a writing
|
|
475
|
+
* caller detect the overrun. Reading callers clamp it themselves. */
|
|
476
|
+
uint64_t len = (end_exclusive > beg) ? end_exclusive - beg : 0;
|
|
477
|
+
*begp = sb_narrow_bit_pos(beg, total);
|
|
478
|
+
*lenp = (ssize_t)(len > SB_MAX_BIT_POS ? SB_MAX_BIT_POS : len);
|
|
479
|
+
return SB_RANGE_OK;
|
|
353
480
|
}
|
|
354
481
|
|
|
355
482
|
static void
|
|
@@ -372,16 +499,20 @@ validate_option_hash(VALUE opts, unsigned allowed)
|
|
|
372
499
|
}
|
|
373
500
|
}
|
|
374
501
|
|
|
502
|
+
/* An absent keyword falls back to the default, but an explicitly passed value
|
|
503
|
+
* must be true or false: `lsb_first: nil` is a caller mistake, not a request
|
|
504
|
+
* for the default, and silently treating it as LSB-first would flip the bit
|
|
505
|
+
* numbering of a getter and its matching setter apart. */
|
|
375
506
|
static int
|
|
376
507
|
parse_bool_opt(VALUE opts, VALUE key, const char *name, int default_value)
|
|
377
508
|
{
|
|
378
509
|
if (NIL_P(opts)) return default_value;
|
|
379
|
-
VALUE value =
|
|
380
|
-
if (
|
|
510
|
+
VALUE value = rb_hash_lookup2(opts, key, Qundef);
|
|
511
|
+
if (value == Qundef) return default_value;
|
|
381
512
|
if (value == Qtrue) return 1;
|
|
382
513
|
if (value == Qfalse) return 0;
|
|
383
514
|
rb_raise(rb_eArgError, "%s must be true or false", name);
|
|
384
|
-
|
|
515
|
+
UNREACHABLE_RETURN(default_value);
|
|
385
516
|
}
|
|
386
517
|
|
|
387
518
|
static int
|
|
@@ -390,48 +521,49 @@ parse_lsb_first_opt(VALUE opts)
|
|
|
390
521
|
return parse_bool_opt(opts, sym_lsb_first, "lsb_first", 1);
|
|
391
522
|
}
|
|
392
523
|
|
|
393
|
-
/* Parse
|
|
394
|
-
*
|
|
524
|
+
/* Parse the optional start_offset positional argument of the iterators
|
|
525
|
+
* (Qnil => 0). A start past the end of the receiver is clamped to its bit
|
|
526
|
+
* length, where every emitter already stops immediately. */
|
|
395
527
|
static ssize_t
|
|
396
|
-
parse_start_offset(VALUE v)
|
|
528
|
+
parse_start_offset(VALUE self, VALUE v)
|
|
397
529
|
{
|
|
398
530
|
if (NIL_P(v)) return 0;
|
|
399
|
-
|
|
400
|
-
if (start_offset < 0)
|
|
401
|
-
rb_raise(rb_eIndexError, "bit_offset must be non-negative");
|
|
402
|
-
return start_offset;
|
|
531
|
+
return sb_narrow_bit_pos(sb_bit_offset(v), SB_BIT_LEN(RSTRING_LEN(self)));
|
|
403
532
|
}
|
|
404
533
|
|
|
405
534
|
/* read -------------------------------------------------------------------- */
|
|
406
535
|
|
|
407
|
-
/*
|
|
408
|
-
|
|
409
|
-
|
|
536
|
+
/* Shared body of bit_get and bit_set?.
|
|
537
|
+
* Returns the bit value (0 or 1), or -1 when the offset is out of range. */
|
|
538
|
+
static int
|
|
539
|
+
str_bit_read(int argc, VALUE *argv, VALUE self)
|
|
410
540
|
{
|
|
411
541
|
VALUE bit_offset_v, opts;
|
|
412
542
|
rb_scan_args(argc, argv, "1:", &bit_offset_v, &opts);
|
|
413
543
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
544
|
+
int lsb_first = parse_lsb_first_opt(opts);
|
|
414
545
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
ssize_t bit_offset = integer_to_bit_idx(bit_offset_v);
|
|
419
|
-
if (bit_offset < 0) {
|
|
420
|
-
rb_raise(rb_eIndexError, "bit index out of range");
|
|
421
|
-
}
|
|
422
|
-
int64_t size = SB_BIT_LEN(RSTRING_LEN(self));
|
|
423
|
-
if (size <= bit_offset) {
|
|
424
|
-
return Qnil;
|
|
425
|
-
}
|
|
546
|
+
ssize_t bit_offset = sb_resolve_bit_offset(self, bit_offset_v);
|
|
547
|
+
if (bit_offset < 0) return -1;
|
|
426
548
|
|
|
427
|
-
|
|
428
|
-
|
|
549
|
+
return test_bit(RSTRING_PTR(self), logical_to_physical(bit_offset, lsb_first));
|
|
550
|
+
}
|
|
429
551
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
552
|
+
/* Return 1/0/nil for the bit at flat position n. */
|
|
553
|
+
static VALUE
|
|
554
|
+
rb_str_bit_get(int argc, VALUE *argv, VALUE self)
|
|
555
|
+
{
|
|
556
|
+
int bit = str_bit_read(argc, argv, self);
|
|
557
|
+
return bit < 0 ? Qnil : INT2FIX(bit);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/* Return true/false/nil for the bit at flat position n. */
|
|
561
|
+
static VALUE
|
|
562
|
+
rb_str_bit_set_p(int argc, VALUE *argv, VALUE self)
|
|
563
|
+
{
|
|
564
|
+
int bit = str_bit_read(argc, argv, self);
|
|
565
|
+
if (bit < 0) return Qnil;
|
|
566
|
+
return bit ? Qtrue : Qfalse;
|
|
435
567
|
}
|
|
436
568
|
|
|
437
569
|
/* count_set_bits: popcount over a raw byte buffer.
|
|
@@ -579,24 +711,20 @@ rb_str_bit_count(int argc, VALUE *argv, VALUE self)
|
|
|
579
711
|
if (rb_obj_is_kind_of(v0, rb_cRange)) {
|
|
580
712
|
if (!NIL_P(v1))
|
|
581
713
|
rb_raise(rb_eArgError, "wrong number of arguments");
|
|
582
|
-
sb_range_validate_endpoints(v0);
|
|
583
714
|
ssize_t beg, len;
|
|
584
|
-
if (
|
|
715
|
+
if (sb_bit_range_beg_len(v0, &beg, &len, total_bits) == SB_RANGE_OUT)
|
|
585
716
|
return INT2FIX(0);
|
|
586
717
|
bit_offset = beg;
|
|
587
718
|
bit_length = len;
|
|
588
719
|
}
|
|
589
720
|
else if (!NIL_P(v1)) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
bit_length = integer_to_bit_idx(v1);
|
|
598
|
-
if (bit_length < 0)
|
|
599
|
-
rb_raise(rb_eArgError, "bit_length must be non-negative");
|
|
721
|
+
uint64_t off = sb_bit_offset(v0);
|
|
722
|
+
uint64_t len = sb_bit_length(v1);
|
|
723
|
+
/* A region starting at or past the end holds no bits, and one that
|
|
724
|
+
* runs off the end contributes only the part that exists. */
|
|
725
|
+
if (off >= (uint64_t)total_bits) return INT2FIX(0);
|
|
726
|
+
bit_offset = sb_narrow_bit_pos(off, total_bits);
|
|
727
|
+
bit_length = sb_clamp_bit_length(len, bit_offset, total_bits);
|
|
600
728
|
}
|
|
601
729
|
else {
|
|
602
730
|
rb_raise(rb_eArgError,
|
|
@@ -659,7 +787,7 @@ rb_str_each_bit(int argc, VALUE *argv, VALUE self)
|
|
|
659
787
|
rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
|
|
660
788
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
661
789
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
662
|
-
ssize_t start_offset = parse_start_offset(start_offset_v);
|
|
790
|
+
ssize_t start_offset = parse_start_offset(self, start_offset_v);
|
|
663
791
|
|
|
664
792
|
emit_bits((const unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self),
|
|
665
793
|
lsb_first, start_offset, Qnil);
|
|
@@ -673,7 +801,7 @@ rb_str_bits(int argc, VALUE *argv, VALUE self)
|
|
|
673
801
|
rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
|
|
674
802
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
675
803
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
676
|
-
ssize_t start_offset = parse_start_offset(start_offset_v);
|
|
804
|
+
ssize_t start_offset = parse_start_offset(self, start_offset_v);
|
|
677
805
|
ssize_t len = RSTRING_LEN(self);
|
|
678
806
|
const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
|
|
679
807
|
|
|
@@ -806,7 +934,7 @@ rb_str_each_bit_offset(int argc, VALUE *argv, VALUE self)
|
|
|
806
934
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
807
935
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
808
936
|
int target = parse_bit_target(bit_val);
|
|
809
|
-
ssize_t start_offset = parse_start_offset(start_offset_v);
|
|
937
|
+
ssize_t start_offset = parse_start_offset(self, start_offset_v);
|
|
810
938
|
|
|
811
939
|
emit_bit_offsets((const unsigned char *)RSTRING_PTR(self), RSTRING_LEN(self),
|
|
812
940
|
target, lsb_first, start_offset, Qnil);
|
|
@@ -821,7 +949,7 @@ rb_str_bit_offsets(int argc, VALUE *argv, VALUE self)
|
|
|
821
949
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
822
950
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
823
951
|
int target = parse_bit_target(bit_val);
|
|
824
|
-
ssize_t start_offset = parse_start_offset(start_offset_v);
|
|
952
|
+
ssize_t start_offset = parse_start_offset(self, start_offset_v);
|
|
825
953
|
|
|
826
954
|
ssize_t len = RSTRING_LEN(self);
|
|
827
955
|
const unsigned char *str = (const unsigned char *)RSTRING_PTR(self);
|
|
@@ -947,23 +1075,21 @@ rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
|
|
|
947
1075
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
948
1076
|
|
|
949
1077
|
if (n_pos == 1 && rb_obj_is_kind_of(v0, rb_cRange)) {
|
|
950
|
-
sb_range_validate_endpoints(v0);
|
|
951
1078
|
ssize_t beg, len;
|
|
952
|
-
if (
|
|
1079
|
+
if (sb_bit_range_beg_len(v0, &beg, &len, total_bits) == SB_RANGE_OUT) {
|
|
953
1080
|
return Qnil;
|
|
954
1081
|
}
|
|
955
1082
|
bit_offset = beg;
|
|
956
1083
|
bit_length = len;
|
|
957
1084
|
}
|
|
958
1085
|
else if (n_pos == 2) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
bit_offset = integer_to_bit_idx(v0);
|
|
964
|
-
bit_length = integer_to_bit_idx(v1);
|
|
1086
|
+
uint64_t off, len;
|
|
1087
|
+
if (sb_bit_position_soft(v0, &off) < 0) return Qnil;
|
|
1088
|
+
if (sb_bit_position_soft(v1, &len) < 0) return Qnil;
|
|
1089
|
+
if (off > (uint64_t)total_bits) return Qnil;
|
|
965
1090
|
|
|
966
|
-
|
|
1091
|
+
bit_offset = sb_narrow_bit_pos(off, total_bits);
|
|
1092
|
+
bit_length = sb_clamp_bit_length(len, bit_offset, total_bits);
|
|
967
1093
|
}
|
|
968
1094
|
else if (n_pos == 1) {
|
|
969
1095
|
return Qnil;
|
|
@@ -982,7 +1108,10 @@ rb_str_bit_slice(int argc, VALUE *argv, VALUE self)
|
|
|
982
1108
|
ssize_t out_bytes = (bit_length + 7) / 8;
|
|
983
1109
|
VALUE result = rb_str_buf_new(out_bytes);
|
|
984
1110
|
rb_str_resize(result, out_bytes);
|
|
985
|
-
|
|
1111
|
+
/* A bit-aligned slice is a repacked byte sequence, not a substring, so the
|
|
1112
|
+
* result carries BINARY like every other String returned by this API. */
|
|
1113
|
+
rb_enc_associate(result, rb_ascii8bit_encoding());
|
|
1114
|
+
ENC_CODERANGE_CLEAR(result);
|
|
986
1115
|
unsigned char *dst = (unsigned char *)RSTRING_PTR(result);
|
|
987
1116
|
const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
|
|
988
1117
|
|
|
@@ -1028,88 +1157,68 @@ rb_str_mutate_bits(int argc, VALUE *argv, VALUE self, enum sb_mutation_op op)
|
|
|
1028
1157
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
1029
1158
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
1030
1159
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
/* 2-arg form: bit_set(bit_offset, bit_length) */
|
|
1047
|
-
if (!rb_integer_type_p(bit_length_v))
|
|
1048
|
-
rb_raise(rb_eTypeError, "bit_length must be an integer");
|
|
1049
|
-
ssize_t bit_offset = integer_to_bit_idx(target);
|
|
1050
|
-
if (bit_offset < 0)
|
|
1051
|
-
rb_raise(rb_eIndexError, "bit_offset must be non-negative");
|
|
1052
|
-
ssize_t bit_length = integer_to_bit_idx(bit_length_v);
|
|
1053
|
-
if (bit_length < 0)
|
|
1054
|
-
rb_raise(rb_eArgError, "bit_length must be non-negative");
|
|
1055
|
-
if (bit_length == 0) return self;
|
|
1056
|
-
int64_t total_bits = SB_BIT_LEN(RSTRING_LEN(self));
|
|
1057
|
-
if (bit_offset >= total_bits || bit_offset + bit_length > total_bits)
|
|
1058
|
-
rb_raise(rb_eIndexError, "bit range out of range");
|
|
1059
|
-
for (ssize_t logical = bit_offset; logical < bit_offset + bit_length; logical++) {
|
|
1060
|
-
ssize_t idx = logical_to_physical(logical, lsb_first);
|
|
1061
|
-
unsigned char mask = (unsigned char)(1u << (idx % 8));
|
|
1062
|
-
switch (op) {
|
|
1063
|
-
case SB_MUT_SET: ptr[idx / 8] |= mask; break;
|
|
1064
|
-
case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
|
|
1065
|
-
case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
|
|
1066
|
-
}
|
|
1160
|
+
unsigned char *ptr;
|
|
1161
|
+
|
|
1162
|
+
/* Single-bit form: bit_set(n). The offset is validated before
|
|
1163
|
+
* rb_str_modify, so an out-of-range offset raises IndexError even on a
|
|
1164
|
+
* frozen receiver, matching the order of the core implementation. */
|
|
1165
|
+
if (NIL_P(bit_length_v) && !rb_obj_is_kind_of(target, rb_cRange)) {
|
|
1166
|
+
ssize_t idx = check_bit_index(self, target, lsb_first);
|
|
1167
|
+
rb_str_modify(self);
|
|
1168
|
+
ptr = (unsigned char *)RSTRING_PTR(self);
|
|
1169
|
+
unsigned char mask = (unsigned char)(1u << (idx % 8));
|
|
1170
|
+
switch (op) {
|
|
1171
|
+
case SB_MUT_SET: ptr[idx / 8] |= mask; break;
|
|
1172
|
+
case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
|
|
1173
|
+
case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
|
|
1067
1174
|
}
|
|
1068
1175
|
return self;
|
|
1069
1176
|
}
|
|
1070
1177
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1178
|
+
int64_t total_bits = SB_BIT_LEN(RSTRING_LEN(self));
|
|
1179
|
+
ssize_t bit_offset, bit_length;
|
|
1073
1180
|
|
|
1074
1181
|
if (rb_obj_is_kind_of(target, rb_cRange)) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
ssize_t beg, len;
|
|
1182
|
+
if (!NIL_P(bit_length_v))
|
|
1183
|
+
rb_raise(rb_eArgError, "wrong number of arguments");
|
|
1078
1184
|
|
|
1079
|
-
/*
|
|
1080
|
-
*
|
|
1081
|
-
if (
|
|
1185
|
+
/* A range that begins past the end has no bits to write, which is an
|
|
1186
|
+
* error for a mutation, not a silent no-op. */
|
|
1187
|
+
if (sb_bit_range_beg_len(target, &bit_offset, &bit_length, total_bits) == SB_RANGE_OUT)
|
|
1082
1188
|
rb_raise(rb_eIndexError, "bit range out of range");
|
|
1083
|
-
}
|
|
1084
1189
|
|
|
1085
|
-
/*
|
|
1086
|
-
*
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1190
|
+
/* sb_bit_range_beg_len clamps the end, so a range that overruns the
|
|
1191
|
+
* string comes back longer than the bits available. Writing is not
|
|
1192
|
+
* allowed to silently shrink, so report the overrun instead. */
|
|
1193
|
+
if ((int64_t)bit_offset + (int64_t)bit_length > total_bits)
|
|
1194
|
+
rb_raise(rb_eIndexError, "bit range out of range");
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
/* 2-arg form: bit_set(bit_offset, bit_length) */
|
|
1198
|
+
uint64_t off = sb_bit_offset(target);
|
|
1199
|
+
uint64_t len = sb_bit_length(bit_length_v);
|
|
1200
|
+
if (len == 0) return self;
|
|
1201
|
+
if (off >= (uint64_t)total_bits || len > (uint64_t)total_bits - off)
|
|
1202
|
+
rb_raise(rb_eIndexError, "bit range out of range");
|
|
1203
|
+
bit_offset = sb_narrow_bit_pos(off, total_bits);
|
|
1204
|
+
if (len > (uint64_t)(sb_addressable_bits(total_bits) - bit_offset))
|
|
1205
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
1206
|
+
bit_length = (ssize_t)len;
|
|
1207
|
+
}
|
|
1098
1208
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1209
|
+
rb_str_modify(self);
|
|
1210
|
+
ptr = (unsigned char *)RSTRING_PTR(self);
|
|
1211
|
+
|
|
1212
|
+
for (ssize_t logical = bit_offset; logical < bit_offset + bit_length; logical++) {
|
|
1213
|
+
ssize_t idx = logical_to_physical(logical, lsb_first);
|
|
1214
|
+
unsigned char mask = (unsigned char)(1u << (idx % 8));
|
|
1215
|
+
switch (op) {
|
|
1216
|
+
case SB_MUT_SET: ptr[idx / 8] |= mask; break;
|
|
1217
|
+
case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
|
|
1218
|
+
case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
|
|
1107
1219
|
}
|
|
1108
|
-
return self;
|
|
1109
1220
|
}
|
|
1110
|
-
|
|
1111
|
-
rb_raise(rb_eTypeError, "bit index must be an integer or Range");
|
|
1112
|
-
UNREACHABLE_RETURN(Qnil);
|
|
1221
|
+
return self;
|
|
1113
1222
|
}
|
|
1114
1223
|
|
|
1115
1224
|
static VALUE
|
|
@@ -1141,13 +1250,20 @@ check_binary_op_lengths(VALUE self, VALUE other)
|
|
|
1141
1250
|
}
|
|
1142
1251
|
}
|
|
1143
1252
|
|
|
1253
|
+
/* Allocate the destination buffer of a non-destructive bitwise operation.
|
|
1254
|
+
*
|
|
1255
|
+
* Bit operations read the receiver as a raw byte sequence, so the bytes they
|
|
1256
|
+
* produce need not be valid in the receiver's encoding; the result is always
|
|
1257
|
+
* BINARY. The destructive (`!`) variants leave the receiver's encoding alone
|
|
1258
|
+
* instead, which is what String#setbyte does for the same reason. */
|
|
1144
1259
|
static VALUE
|
|
1145
1260
|
alloc_result(VALUE self)
|
|
1146
1261
|
{
|
|
1147
1262
|
ssize_t len = RSTRING_LEN(self);
|
|
1148
1263
|
VALUE result = rb_str_buf_new(len);
|
|
1149
1264
|
rb_str_resize(result, len);
|
|
1150
|
-
rb_enc_associate(result,
|
|
1265
|
+
rb_enc_associate(result, rb_ascii8bit_encoding());
|
|
1266
|
+
ENC_CODERANGE_CLEAR(result);
|
|
1151
1267
|
return result;
|
|
1152
1268
|
}
|
|
1153
1269
|
|
|
@@ -1262,6 +1378,7 @@ SB_DEFINE_BINARY_KERNEL(kern_xor, SB_XOR_WORD, SB_XOR_BYTE)
|
|
|
1262
1378
|
static VALUE \
|
|
1263
1379
|
rb_str_bitwise_##op_name(VALUE self, VALUE other) \
|
|
1264
1380
|
{ \
|
|
1381
|
+
StringValue(other); \
|
|
1265
1382
|
check_binary_op_lengths(self, other); \
|
|
1266
1383
|
ssize_t len = RSTRING_LEN(self); \
|
|
1267
1384
|
VALUE result = alloc_result(self); \
|
|
@@ -1273,6 +1390,7 @@ SB_DEFINE_BINARY_KERNEL(kern_xor, SB_XOR_WORD, SB_XOR_BYTE)
|
|
|
1273
1390
|
static VALUE \
|
|
1274
1391
|
rb_str_bitwise_##op_name##_bang(VALUE self, VALUE other) \
|
|
1275
1392
|
{ \
|
|
1393
|
+
StringValue(other); \
|
|
1276
1394
|
check_binary_op_lengths(self, other); \
|
|
1277
1395
|
rb_str_modify(self); \
|
|
1278
1396
|
ssize_t len = RSTRING_LEN(self); \
|
|
@@ -1469,7 +1587,7 @@ rb_str_bit_fields(int argc, VALUE *argv, VALUE self)
|
|
|
1469
1587
|
* - remaining bytes: ctz on the inverted byte
|
|
1470
1588
|
*
|
|
1471
1589
|
* Porting to Ruby Core:
|
|
1472
|
-
* 1. Move to string.c alongside
|
|
1590
|
+
* 1. Move to string.c alongside bit_get and each_bit.
|
|
1473
1591
|
* 2. Share sb_ctz8 / sb_ctzll with the existing set-bit helpers.
|
|
1474
1592
|
*/
|
|
1475
1593
|
static ssize_t
|
|
@@ -1541,9 +1659,15 @@ rb_str_bit_run_count(int argc, VALUE *argv, VALUE self)
|
|
|
1541
1659
|
rb_raise(rb_eTypeError, "position must be an integer");
|
|
1542
1660
|
}
|
|
1543
1661
|
int target = parse_bit_target(bit_val);
|
|
1544
|
-
ssize_t bit_offset = integer_to_bit_idx(bit_offset_v);
|
|
1545
1662
|
ssize_t src_len = RSTRING_LEN(self);
|
|
1546
|
-
|
|
1663
|
+
/* A position with no run starting at it and a position outside the string
|
|
1664
|
+
* are the same answer here -- nil -- so a negative or past-the-end offset
|
|
1665
|
+
* is not an error (see ProposedMethods.md, "Why bit_run_count never
|
|
1666
|
+
* returns 0"). */
|
|
1667
|
+
uint64_t off;
|
|
1668
|
+
if (sb_bit_position_soft(bit_offset_v, &off) < 0) return Qnil;
|
|
1669
|
+
if (off >= (uint64_t)SB_BIT_LEN(src_len)) return Qnil;
|
|
1670
|
+
ssize_t bit_offset = (ssize_t)off;
|
|
1547
1671
|
|
|
1548
1672
|
const unsigned char *src = (const unsigned char *)RSTRING_PTR(self);
|
|
1549
1673
|
if (lsb_first) {
|
|
@@ -1618,7 +1742,7 @@ rb_str_each_bit_run(int argc, VALUE *argv, VALUE self)
|
|
|
1618
1742
|
rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
|
|
1619
1743
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
1620
1744
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
1621
|
-
ssize_t start_offset = parse_start_offset(start_offset_v);
|
|
1745
|
+
ssize_t start_offset = parse_start_offset(self, start_offset_v);
|
|
1622
1746
|
|
|
1623
1747
|
emit_bit_runs(self, lsb_first, start_offset, Qnil);
|
|
1624
1748
|
return self;
|
|
@@ -1632,7 +1756,7 @@ rb_str_bit_runs(int argc, VALUE *argv, VALUE self)
|
|
|
1632
1756
|
rb_scan_args(argc, argv, "01:", &start_offset_v, &opts);
|
|
1633
1757
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
1634
1758
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
1635
|
-
ssize_t start_offset = parse_start_offset(start_offset_v);
|
|
1759
|
+
ssize_t start_offset = parse_start_offset(self, start_offset_v);
|
|
1636
1760
|
|
|
1637
1761
|
if (rb_block_given_p()) {
|
|
1638
1762
|
emit_bit_runs(self, lsb_first, start_offset, Qnil);
|
|
@@ -1649,7 +1773,7 @@ static VALUE
|
|
|
1649
1773
|
rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
|
|
1650
1774
|
{
|
|
1651
1775
|
ssize_t dst_bit_off, dst_bit_len;
|
|
1652
|
-
ssize_t src_bit_off
|
|
1776
|
+
ssize_t src_bit_off;
|
|
1653
1777
|
VALUE str;
|
|
1654
1778
|
int64_t dst_total = SB_BIT_LEN(RSTRING_LEN(self));
|
|
1655
1779
|
VALUE v0, v1, v2, v3, opts;
|
|
@@ -1658,43 +1782,38 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
|
|
|
1658
1782
|
validate_option_hash(opts, SB_KW_LSB_FIRST);
|
|
1659
1783
|
int lsb_first = parse_lsb_first_opt(opts);
|
|
1660
1784
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
sb_range_beg_len(v0, &beg, &len, dst_total, 1);
|
|
1666
|
-
dst_bit_off = beg;
|
|
1667
|
-
dst_bit_len = len;
|
|
1668
|
-
str = v1;
|
|
1669
|
-
Check_Type(str, T_STRING);
|
|
1670
|
-
src_bit_off = 0;
|
|
1671
|
-
src_bit_len = dst_bit_len;
|
|
1672
|
-
}
|
|
1673
|
-
else if (n_pos == 3 && rb_obj_is_kind_of(v0, rb_cRange)) {
|
|
1674
|
-
/* bit_splice(range, str, str_bit_index) */
|
|
1675
|
-
sb_range_validate_endpoints(v0);
|
|
1785
|
+
uint64_t dst_off_u, dst_len_u, src_off_u, src_len_u;
|
|
1786
|
+
|
|
1787
|
+
if (n_pos <= 3 && rb_obj_is_kind_of(v0, rb_cRange)) {
|
|
1788
|
+
/* bit_splice(range, str) and bit_splice(range, str, str_bit_index) */
|
|
1676
1789
|
ssize_t beg, len;
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1790
|
+
if (sb_bit_range_beg_len(v0, &beg, &len, dst_total) == SB_RANGE_OUT) {
|
|
1791
|
+
rb_raise(rb_eIndexError,
|
|
1792
|
+
"bit_splice: destination range starts past the end "
|
|
1793
|
+
"(total %" PRId64 " bits)", (int64_t)dst_total);
|
|
1794
|
+
}
|
|
1795
|
+
dst_off_u = (uint64_t)beg;
|
|
1796
|
+
dst_len_u = (uint64_t)len;
|
|
1680
1797
|
str = v1;
|
|
1681
1798
|
Check_Type(str, T_STRING);
|
|
1682
|
-
if (
|
|
1683
|
-
|
|
1799
|
+
if (n_pos == 3) {
|
|
1800
|
+
if (!rb_integer_type_p(v2)) {
|
|
1801
|
+
rb_raise(rb_eTypeError, "third argument must be an Integer");
|
|
1802
|
+
}
|
|
1803
|
+
src_off_u = sb_bit_offset(v2);
|
|
1684
1804
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1805
|
+
else {
|
|
1806
|
+
src_off_u = 0;
|
|
1807
|
+
}
|
|
1808
|
+
src_len_u = dst_len_u;
|
|
1689
1809
|
}
|
|
1690
1810
|
else if (n_pos == 3) {
|
|
1691
1811
|
/* bit_splice(bit_index, bit_length, str) */
|
|
1692
1812
|
if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1)) {
|
|
1693
1813
|
rb_raise(rb_eTypeError, "bit index and length must be integers");
|
|
1694
1814
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
if (dst_bit_off < 0) dst_bit_off += dst_total;
|
|
1815
|
+
dst_off_u = sb_bit_offset(v0);
|
|
1816
|
+
dst_len_u = sb_bit_length(v1);
|
|
1698
1817
|
|
|
1699
1818
|
/*
|
|
1700
1819
|
* Integer source support was prototyped here, but it is intentionally
|
|
@@ -1708,42 +1827,56 @@ rb_str_bit_splice(int argc, VALUE *argv, VALUE self)
|
|
|
1708
1827
|
|
|
1709
1828
|
str = v2;
|
|
1710
1829
|
Check_Type(str, T_STRING);
|
|
1711
|
-
|
|
1712
|
-
|
|
1830
|
+
src_off_u = 0;
|
|
1831
|
+
src_len_u = dst_len_u;
|
|
1713
1832
|
}
|
|
1714
1833
|
else if (n_pos == 4) {
|
|
1715
1834
|
/* bit_splice(bit_index, bit_length, str, str_bit_index) */
|
|
1716
1835
|
if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1) || !rb_integer_type_p(v3)) {
|
|
1717
1836
|
rb_raise(rb_eTypeError, "bit indices and lengths must be integers");
|
|
1718
1837
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
if (dst_bit_off < 0) dst_bit_off += dst_total;
|
|
1838
|
+
dst_off_u = sb_bit_offset(v0);
|
|
1839
|
+
dst_len_u = sb_bit_length(v1);
|
|
1722
1840
|
str = v2;
|
|
1723
1841
|
Check_Type(str, T_STRING);
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
if (src_bit_off < 0) src_bit_off += src_total;
|
|
1727
|
-
src_bit_len = dst_bit_len;
|
|
1842
|
+
src_off_u = sb_bit_offset(v3);
|
|
1843
|
+
src_len_u = dst_len_u;
|
|
1728
1844
|
}
|
|
1729
1845
|
else {
|
|
1730
1846
|
rb_raise(rb_eArgError,
|
|
1731
1847
|
"wrong number of arguments (given %d, expected 2, 3, or 4)", n_pos);
|
|
1732
1848
|
}
|
|
1733
1849
|
|
|
1734
|
-
|
|
1850
|
+
/* Both ranges are checked in 64 bits: a position past the end of the
|
|
1851
|
+
* string is well-formed input here, so it must reach this bounds check
|
|
1852
|
+
* rather than being rejected while it is parsed. */
|
|
1853
|
+
if (dst_off_u > (uint64_t)dst_total || dst_len_u > (uint64_t)dst_total - dst_off_u) {
|
|
1735
1854
|
rb_raise(rb_eIndexError,
|
|
1736
|
-
"bit_splice: destination range [%"
|
|
1855
|
+
"bit_splice: destination range [%" PRIu64 ", %" PRIu64
|
|
1737
1856
|
"] out of bounds (total %" PRId64 " bits)",
|
|
1738
|
-
|
|
1857
|
+
dst_off_u, dst_len_u, (int64_t)dst_total);
|
|
1739
1858
|
}
|
|
1740
1859
|
|
|
1741
1860
|
int64_t src_total_bits = SB_BIT_LEN(RSTRING_LEN(str));
|
|
1742
|
-
if (
|
|
1861
|
+
if (src_off_u > (uint64_t)src_total_bits ||
|
|
1862
|
+
src_len_u > (uint64_t)src_total_bits - src_off_u) {
|
|
1743
1863
|
rb_raise(rb_eIndexError,
|
|
1744
|
-
"bit_splice: source range [%"
|
|
1864
|
+
"bit_splice: source range [%" PRIu64 ", %" PRIu64
|
|
1745
1865
|
"] out of bounds (total %" PRId64 " bits)",
|
|
1746
|
-
|
|
1866
|
+
src_off_u, src_len_u, (int64_t)src_total_bits);
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/* Both ranges are inside their strings now, so narrowing only fails on an
|
|
1870
|
+
* ILP32 build whose bit length exceeds the internal index (see
|
|
1871
|
+
* sb_narrow_bit_pos). */
|
|
1872
|
+
dst_bit_off = sb_narrow_bit_pos(dst_off_u, dst_total);
|
|
1873
|
+
dst_bit_len = (ssize_t)dst_len_u;
|
|
1874
|
+
if (dst_len_u > (uint64_t)(sb_addressable_bits(dst_total) - dst_bit_off)) {
|
|
1875
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
1876
|
+
}
|
|
1877
|
+
src_bit_off = sb_narrow_bit_pos(src_off_u, src_total_bits);
|
|
1878
|
+
if (src_len_u > (uint64_t)(sb_addressable_bits(src_total_bits) - src_bit_off)) {
|
|
1879
|
+
rb_raise(rb_eArgError, "bit index out of representable range");
|
|
1747
1880
|
}
|
|
1748
1881
|
|
|
1749
1882
|
if (dst_bit_len == 0) return self;
|
|
@@ -1951,7 +2084,8 @@ Init_string_bits(void)
|
|
|
1951
2084
|
sym_lsb_first = ID2SYM(rb_intern("lsb_first"));
|
|
1952
2085
|
sym_invert = ID2SYM(rb_intern("invert"));
|
|
1953
2086
|
|
|
1954
|
-
rb_define_method(rb_cString, "
|
|
2087
|
+
rb_define_method(rb_cString, "bit_get", rb_str_bit_get, -1);
|
|
2088
|
+
rb_define_method(rb_cString, "bit_set?", rb_str_bit_set_p, -1);
|
|
1955
2089
|
rb_define_method(rb_cString, "bit_count", rb_str_bit_count, -1);
|
|
1956
2090
|
rb_define_method(rb_cString, "each_bit", rb_str_each_bit, -1);
|
|
1957
2091
|
rb_define_method(rb_cString, "bits", rb_str_bits, -1);
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: string_bits
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- HASUMI Hitoshi
|
|
@@ -91,6 +91,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
91
91
|
- - ">="
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
93
|
version: '3.0'
|
|
94
|
+
- - "<"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 4.1.dev
|
|
94
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
98
|
requirements:
|
|
96
99
|
- - ">="
|