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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/ext/string_bits/string_bits.c +399 -265
  4. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6202692ddb08678e3c0c07f9f2e1f15a85982fbe172e9d1e986559cc954efbe
4
- data.tar.gz: a23c8747fec6bdbb82565bd299851be0d6c2f5150e148b5b6ce5b1c1b356c1c1
3
+ metadata.gz: c21407d901f599aa0d33fe0bad85d39f4729da08c656cab3556fc087198394f2
4
+ data.tar.gz: e4c8912125bfd9f4a6fcfce0be0d78f528f0bb87369064cfc1c96b4d3f5b7ac9
5
5
  SHA512:
6
- metadata.gz: ce535ad3b8066edce1c3f132cfad48b3e958d6252a42bee210a7bd5ef8962c235383cab7bae0d79a1d600ab937a9d7197ec08590962884af8b8b2bdc92b3cffb
7
- data.tar.gz: 05dd0dd9327fa659a171cb5645a792a5442ec09e47564c64d8e635ca54bfcdef3ef67f8e9e44f1282bd17fdd557874bea96495a03abb85ea6bd3c905678ba35b
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
- 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_at(n, lsb_first: false) }` stays true.
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".bit_at(10)
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
- /* Convert a Ruby Integer to a ssize_t bit index.
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
- * RBIGNUM_NEGATIVE_P is available via ruby.h -> ruby/internal/core/rbignum.h. */
214
- static ssize_t
215
- integer_to_bit_idx(VALUE n)
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
- if (FIXNUM_P(n)) return NUM2SSIZET(n);
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
- rb_raise(rb_eArgError, "bit index out of representable range");
220
- UNREACHABLE_RETURN(0);
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
- if (!rb_integer_type_p(n)) {
257
- rb_raise(rb_eTypeError, "bit index must be an integer");
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
- /* ssize_t-interface wrapper around rb_range_beg_len.
420
+ /* Resolve a bit Range against the receiver's bit length.
268
421
  *
269
- * rb_range_beg_len() takes (long *begp, long *lenp, long len), but this
270
- * extension uses ssize_t throughout for LP64/LLP64 uniformity. On Windows
271
- * (LLP64) long is 32-bit while ssize_t is 64-bit, so passing &ssize_t to the
272
- * stock API is a type error and the build fails. This wrapper bridges the
273
- * two and clamps the input length to LONG_MAX on platforms where ssize_t
274
- * is wider than long, which has no practical effect: a 2 GiB string is
275
- * already past what any realistic caller will hand us.
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
- * It also catches the RangeError that rb_range_beg_len raises when a Range
278
- * endpoint is a Bignum too large for `long`, and re-raises it as IndexError
279
- * so that out-of-range bit positions report uniformly across LP64 and LLP64.
280
- */
281
- struct sb_range_args {
282
- VALUE range;
283
- long *lbegp;
284
- long *llenp;
285
- long len;
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
- static void
313
- sb_range_validate_endpoints(VALUE range)
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 beg, end;
455
+ VALUE beg_v, end_v;
316
456
  int excl;
317
- rb_range_values(range, &beg, &end, &excl);
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
- static inline VALUE
335
- sb_range_beg_len(VALUE range, ssize_t *begp, ssize_t *lenp, int64_t len, int err)
336
- {
337
- long lbeg = 0, llen = 0;
338
- long clipped = (len > (int64_t)LONG_MAX) ? LONG_MAX : (long)len;
339
- struct sb_range_args args = { range, &lbeg, &llen, clipped, err };
340
- int state = 0;
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
- if (begp) *begp = (ssize_t)lbeg;
351
- if (lenp) *lenp = (ssize_t)llen;
352
- return result;
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 = rb_hash_aref(opts, key);
380
- if (NIL_P(value)) return default_value;
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
- return default_value;
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 an optional start_offset positional argument (Qnil => 0).
394
- * Raises ArgumentError for Bignum, IndexError for negative Fixnum. */
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
- ssize_t start_offset = integer_to_bit_idx(v); /* raises ArgumentError for Bignum */
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
- /* Return true/false/nil for the bit at flat position n. */
408
- static VALUE
409
- rb_str_bit_at(int argc, VALUE *argv, VALUE self)
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
- if (!rb_integer_type_p(bit_offset_v)) {
416
- rb_raise(rb_eTypeError, "bit index must be an integer");
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
- int lsb_first = parse_lsb_first_opt(opts);
428
- ssize_t idx = logical_to_physical(bit_offset, lsb_first);
549
+ return test_bit(RSTRING_PTR(self), logical_to_physical(bit_offset, lsb_first));
550
+ }
429
551
 
430
- if (test_bit(RSTRING_PTR(self), idx)) {
431
- return Qtrue;
432
- } else {
433
- return Qfalse;
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 (!RTEST(sb_range_beg_len(v0, &beg, &len, total_bits, 0)))
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
- if (!rb_integer_type_p(v0))
591
- rb_raise(rb_eTypeError, "bit_offset must be an integer");
592
- if (!rb_integer_type_p(v1))
593
- rb_raise(rb_eTypeError, "bit_length must be an integer");
594
- bit_offset = integer_to_bit_idx(v0);
595
- if (bit_offset < 0)
596
- rb_raise(rb_eIndexError, "bit_offset must be non-negative");
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 (!RTEST(sb_range_beg_len(v0, &beg, &len, total_bits, 0))) {
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
- if (!rb_integer_type_p(v0) || !rb_integer_type_p(v1)) {
960
- return Qnil;
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
- if (bit_offset < 0 || bit_length < 0) return Qnil;
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
- rb_enc_associate(result, rb_enc_get(self));
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
- rb_str_modify(self);
1032
- unsigned char *ptr = (unsigned char *)RSTRING_PTR(self);
1033
-
1034
- if (rb_integer_type_p(target)) {
1035
- if (NIL_P(bit_length_v)) {
1036
- /* Single-bit form: bit_set(n) */
1037
- ssize_t idx = check_bit_index(self, target, lsb_first);
1038
- unsigned char mask = (unsigned char)(1u << (idx % 8));
1039
- switch (op) {
1040
- case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1041
- case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1042
- case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
1043
- }
1044
- return self;
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
- if (!NIL_P(bit_length_v))
1072
- rb_raise(rb_eArgError, "wrong number of arguments");
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
- sb_range_validate_endpoints(target);
1076
- int64_t total_bits = SB_BIT_LEN(RSTRING_LEN(self));
1077
- ssize_t beg, len;
1182
+ if (!NIL_P(bit_length_v))
1183
+ rb_raise(rb_eArgError, "wrong number of arguments");
1078
1184
 
1079
- /* err=0 returns Qnil for out-of-range begin (after negative normalization);
1080
- * convert that to IndexError to stay consistent with single-bit access. */
1081
- if (!RTEST(sb_range_beg_len(target, &beg, &len, total_bits, 0))) {
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
- /* err=0 silently clamps end > total. Detect that and raise instead,
1086
- * to stay consistent with bit_splice and single-bit mutation. */
1087
- VALUE rng_beg_unused, rng_end_v;
1088
- int excl;
1089
- rb_range_values(target, &rng_beg_unused, &rng_end_v, &excl);
1090
- (void)rng_beg_unused;
1091
- if (!NIL_P(rng_end_v)) {
1092
- ssize_t end_val = integer_to_bit_idx(rng_end_v);
1093
- ssize_t end_excl = excl ? end_val : end_val + 1;
1094
- if (end_excl > total_bits) {
1095
- rb_raise(rb_eIndexError, "bit range out of range");
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
- for (ssize_t logical = beg; logical < beg + len; logical++) {
1100
- ssize_t idx = logical_to_physical(logical, lsb_first);
1101
- unsigned char mask = (unsigned char)(1u << (idx % 8));
1102
- switch (op) {
1103
- case SB_MUT_SET: ptr[idx / 8] |= mask; break;
1104
- case SB_MUT_CLEAR: ptr[idx / 8] &= (unsigned char)~mask; break;
1105
- case SB_MUT_FLIP: ptr[idx / 8] ^= mask; break;
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, rb_enc_get(self));
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 bit_at and each_bit.
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
- if (bit_offset < 0 || bit_offset >= SB_BIT_LEN(src_len)) return Qnil;
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, src_bit_len;
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
- if (n_pos == 2 && rb_obj_is_kind_of(v0, rb_cRange)) {
1662
- /* bit_splice(range, str) */
1663
- sb_range_validate_endpoints(v0);
1664
- ssize_t beg, len;
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
- sb_range_beg_len(v0, &beg, &len, dst_total, 1);
1678
- dst_bit_off = beg;
1679
- dst_bit_len = len;
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 (!rb_integer_type_p(v2)) {
1683
- rb_raise(rb_eTypeError, "third argument must be an Integer");
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
- int64_t src_total = SB_BIT_LEN(RSTRING_LEN(str));
1686
- src_bit_off = integer_to_bit_idx(v2);
1687
- if (src_bit_off < 0) src_bit_off += src_total;
1688
- src_bit_len = dst_bit_len;
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
- dst_bit_off = integer_to_bit_idx(v0);
1696
- dst_bit_len = integer_to_bit_idx(v1);
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
- src_bit_off = 0;
1712
- src_bit_len = dst_bit_len;
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
- dst_bit_off = integer_to_bit_idx(v0);
1720
- dst_bit_len = integer_to_bit_idx(v1);
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
- int64_t src_total = SB_BIT_LEN(RSTRING_LEN(str));
1725
- src_bit_off = integer_to_bit_idx(v3);
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
- if (dst_bit_off < 0 || dst_bit_len < 0 || dst_bit_off + dst_bit_len > dst_total) {
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 [%" PRIdPTR ", %" PRIdPTR
1855
+ "bit_splice: destination range [%" PRIu64 ", %" PRIu64
1737
1856
  "] out of bounds (total %" PRId64 " bits)",
1738
- (intptr_t)dst_bit_off, (intptr_t)dst_bit_len, (int64_t)dst_total);
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 (src_bit_off < 0 || src_bit_len < 0 || src_bit_off + src_bit_len > src_total_bits) {
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 [%" PRIdPTR ", %" PRIdPTR
1864
+ "bit_splice: source range [%" PRIu64 ", %" PRIu64
1745
1865
  "] out of bounds (total %" PRId64 " bits)",
1746
- (intptr_t)src_bit_off, (intptr_t)src_bit_len, (int64_t)src_total_bits);
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, "bit_at", rb_str_bit_at, -1);
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.2.1
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
  - - ">="