Add several new methods for getting and setting buffer contents. (#6434)

This commit is contained in:
Samuel Williams 2022-09-26 18:06:12 +13:00 committed by GitHub
parent ecffc6a203
commit 025b8701c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
Notes: git 2022-09-26 14:06:43 +09:00
Merged-By: ioquatix <samuel@codeotaku.com>
5 changed files with 503 additions and 115 deletions

27
benchmark/buffer_each.yml Normal file
View File

@ -0,0 +1,27 @@
prelude: |
# frozen_string_literal: true
Warning[:experimental] = false
string = "The quick brown fox jumped over the lazy dog."
array = string.bytes
buffer = IO::Buffer.for(string)
benchmark:
string.each_byte: |
upcased = String.new
string.each_byte do |byte|
upcased << (byte ^ 32)
end
array.each: |
upcased = String.new
array.each do |byte|
upcased << (byte ^ 32)
end
buffer.each: |
upcased = String.new
buffer.each(:U8) do |offset, byte|
upcased << (byte ^ 32)
end
buffer.each_byte: |
upcased = String.new
buffer.each_byte do |byte|
upcased << (byte ^ 32)
end

View File

@ -1,10 +1,25 @@
prelude: |
# frozen_string_literal: true
Warning[:experimental] = false
buffer = IO::Buffer.new(32, IO::Buffer::MAPPED)
string = "\0" * 32
string = "The quick brown fox jumped over the lazy dog."
buffer = IO::Buffer.for(string)
format = [:U32, :U32, :U32, :U32]
benchmark:
buffer.get_value: |
buffer.get_value(:U32, 0)
string.unpack1: |
string.unpack1("N")
[
string.unpack1("N"),
string.unpack1("N", offset: 4),
string.unpack1("N", offset: 8),
string.unpack1("N", offset: 12),
]
buffer.get_value: |
[
buffer.get_value(:U32, 0),
buffer.get_value(:U32, 4),
buffer.get_value(:U32, 8),
buffer.get_value(:U32, 12),
]
buffer.get_values: |
buffer.get_values(format, 0)
string.unpack: |
string.unpack("NNNN")

View File

@ -7669,6 +7669,7 @@ io.$(OBJEXT): {$(VPATH)}util.h
io.$(OBJEXT): {$(VPATH)}vm_core.h
io.$(OBJEXT): {$(VPATH)}vm_opts.h
io_buffer.$(OBJEXT): $(hdrdir)/ruby/ruby.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/array.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/compilers.h
io_buffer.$(OBJEXT): $(top_srcdir)/internal/error.h

View File

@ -6,6 +6,7 @@
**********************************************************************/
#include "internal/array.h"
#include "ruby/io.h"
#include "ruby/io/buffer.h"
#include "ruby/fiber/scheduler.h"
@ -461,7 +462,6 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags
*
* Note that some operating systems may not have cache coherency between mapped
* buffers and file reads.
*
*/
static VALUE
io_buffer_map(int argc, VALUE *argv, VALUE klass)
@ -543,7 +543,6 @@ io_flags_for_size(size_t size)
* # =>
* # #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
* # 0x00000000 74 65 73 74 test
*
*/
VALUE
rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
@ -628,7 +627,6 @@ io_buffer_validate(struct rb_io_buffer *data)
*
* puts IO::Buffer.new(4) # uses to_s internally
* # #<IO::Buffer 0x000055769f41b1a0+4 INTERNAL>
*
*/
VALUE
rb_io_buffer_to_s(VALUE self)
@ -756,7 +754,6 @@ rb_io_buffer_inspect(VALUE self)
*
* Returns the size of the buffer that was explicitly set (on creation with ::new
* or on #resize), or deduced on buffer's creation from string or file.
*
*/
VALUE
rb_io_buffer_size(VALUE self)
@ -774,7 +771,6 @@ rb_io_buffer_size(VALUE self)
*
* A buffer becomes invalid if it is a slice of another buffer which has been
* freed.
*
*/
static VALUE
rb_io_buffer_valid_p(VALUE self)
@ -790,7 +786,6 @@ rb_io_buffer_valid_p(VALUE self)
*
* If the buffer was freed with #free or was never allocated in the first
* place.
*
*/
static VALUE
rb_io_buffer_null_p(VALUE self)
@ -807,7 +802,6 @@ rb_io_buffer_null_p(VALUE self)
* If the buffer has 0 size: it is created by ::new with size 0, or with ::for
* from an empty string. (Note that empty files can't be mapped, so the buffer
* created with ::map will never be empty.)
*
*/
static VALUE
rb_io_buffer_empty_p(VALUE self)
@ -828,7 +822,6 @@ rb_io_buffer_empty_p(VALUE self)
* memory.
*
* External buffer can't be resized.
*
*/
static VALUE
rb_io_buffer_external_p(VALUE self)
@ -854,7 +847,6 @@ rb_io_buffer_external_p(VALUE self)
*
* Internal buffers can be resized, and such an operation will typically
* invalidate all slices, but not always.
*
*/
static VALUE
rb_io_buffer_internal_p(VALUE self)
@ -877,7 +869,6 @@ rb_io_buffer_internal_p(VALUE self)
*
* Mapped buffers can usually be resized, and such an operation will typically
* invalidate all slices, but not always.
*
*/
static VALUE
rb_io_buffer_mapped_p(VALUE self)
@ -901,7 +892,6 @@ rb_io_buffer_mapped_p(VALUE self)
* buffer.locked do
* buffer.write(io) # theoretical system call interface
* end
*
*/
static VALUE
rb_io_buffer_locked_p(VALUE self)
@ -928,7 +918,6 @@ rb_io_buffer_readonly_p(VALUE self)
* #set_value, #set_string or #copy and similar.
*
* Frozen strings and read-only files create read-only buffers.
*
*/
static VALUE
io_buffer_readonly_p(VALUE self)
@ -1053,7 +1042,6 @@ rb_io_buffer_locked(VALUE self)
* # => true
*
* You can resize a freed buffer to re-allocate it.
*
*/
VALUE
rb_io_buffer_free(VALUE self)
@ -1115,7 +1103,6 @@ io_buffer_validate_range(struct rb_io_buffer *data, size_t offset, size_t length
* # ...and original string
* string
* # => tost
*
*/
VALUE
rb_io_buffer_slice(VALUE self, VALUE _offset, VALUE _length)
@ -1166,7 +1153,7 @@ rb_io_buffer_get_bytes(VALUE self, void **base, size_t *size)
return 0;
}
static void
inline static void
io_buffer_get_bytes_for_writing(struct rb_io_buffer *data, void **base, size_t *size)
{
if (data->flags & RB_IO_BUFFER_READONLY) {
@ -1238,7 +1225,6 @@ rb_io_buffer_get_bytes_for_reading(VALUE self, const void **base, size_t *size)
* # #<IO::Buffer 0x0000000000000000+0 NULL>
* buffer.null?
* # => true
*
*/
VALUE
rb_io_buffer_transfer(VALUE self)
@ -1358,7 +1344,6 @@ rb_io_buffer_resize(VALUE self, size_t size)
*
* External buffer (created with ::for), and locked buffer
* can not be resized.
*
*/
static VALUE
io_buffer_resize(VALUE self, VALUE size)
@ -1373,7 +1358,6 @@ io_buffer_resize(VALUE self, VALUE size)
*
* Buffers are compared by size and exact contents of the memory they are
* referencing using +memcmp+.
*
*/
static VALUE
rb_io_buffer_compare(VALUE self, VALUE other)
@ -1399,7 +1383,7 @@ static void
io_buffer_validate_type(size_t size, size_t offset)
{
if (offset > size) {
rb_raise(rb_eArgError, "Type extends beyond end of buffer!");
rb_raise(rb_eArgError, "Type extends beyond end of buffer! (offset=%ld > size=%ld)", offset, size);
}
}
@ -1449,8 +1433,8 @@ ruby_swapf64(double value)
return swap.value;
}
#define DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \
static ID RB_IO_BUFFER_TYPE_##name; \
#define IO_BUFFER_DECLARE_TYPE(name, type, endian, wrap, unwrap, swap) \
static ID RB_IO_BUFFER_DATA_TYPE_##name; \
\
static VALUE \
io_buffer_read_##name(const void* base, size_t size, size_t *offset) \
@ -1471,67 +1455,123 @@ io_buffer_write_##name(const void* base, size_t size, size_t *offset, VALUE _val
if (endian != RB_IO_BUFFER_HOST_ENDIAN) value = swap(value); \
memcpy((char*)base + *offset, &value, sizeof(type)); \
*offset += sizeof(type); \
}
} \
\
enum { \
RB_IO_BUFFER_DATA_TYPE_##name##_SIZE = sizeof(type) \
};
DECLARE_TYPE(U8, uint8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap8)
DECLARE_TYPE(S8, int8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap8)
IO_BUFFER_DECLARE_TYPE(U8, uint8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap8)
IO_BUFFER_DECLARE_TYPE(S8, int8_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap8)
DECLARE_TYPE(u16, uint16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
DECLARE_TYPE(U16, uint16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
DECLARE_TYPE(s16, int16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
DECLARE_TYPE(S16, int16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
IO_BUFFER_DECLARE_TYPE(u16, uint16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
IO_BUFFER_DECLARE_TYPE(U16, uint16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap16)
IO_BUFFER_DECLARE_TYPE(s16, int16_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
IO_BUFFER_DECLARE_TYPE(S16, int16_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap16)
DECLARE_TYPE(u32, uint32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
DECLARE_TYPE(U32, uint32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
DECLARE_TYPE(s32, int32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
DECLARE_TYPE(S32, int32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
IO_BUFFER_DECLARE_TYPE(u32, uint32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
IO_BUFFER_DECLARE_TYPE(U32, uint32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_UINT2NUM, RB_NUM2UINT, ruby_swap32)
IO_BUFFER_DECLARE_TYPE(s32, int32_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
IO_BUFFER_DECLARE_TYPE(S32, int32_t, RB_IO_BUFFER_BIG_ENDIAN, RB_INT2NUM, RB_NUM2INT, ruby_swap32)
DECLARE_TYPE(u64, uint64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
IO_BUFFER_DECLARE_TYPE(u64, uint64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
IO_BUFFER_DECLARE_TYPE(U64, uint64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_ULL2NUM, RB_NUM2ULL, ruby_swap64)
IO_BUFFER_DECLARE_TYPE(s64, int64_t, RB_IO_BUFFER_LITTLE_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
IO_BUFFER_DECLARE_TYPE(S64, int64_t, RB_IO_BUFFER_BIG_ENDIAN, RB_LL2NUM, RB_NUM2LL, ruby_swap64)
DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
#undef DECLARE_TYPE
IO_BUFFER_DECLARE_TYPE(f32, float, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
IO_BUFFER_DECLARE_TYPE(F32, float, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf32)
IO_BUFFER_DECLARE_TYPE(f64, double, RB_IO_BUFFER_LITTLE_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
IO_BUFFER_DECLARE_TYPE(F64, double, RB_IO_BUFFER_BIG_ENDIAN, DBL2NUM, NUM2DBL, ruby_swapf64)
#undef IO_BUFFER_DECLARE_TYPE
VALUE
rb_io_buffer_get_value(const void* base, size_t size, ID type, size_t offset)
{
#define READ_TYPE(name) if (type == RB_IO_BUFFER_TYPE_##name) return io_buffer_read_##name(base, size, &offset);
READ_TYPE(U8);
READ_TYPE(S8);
READ_TYPE(u16);
READ_TYPE(U16);
READ_TYPE(s16);
READ_TYPE(S16);
READ_TYPE(u32);
READ_TYPE(U32);
READ_TYPE(s32);
READ_TYPE(S32);
READ_TYPE(u64);
READ_TYPE(U64);
READ_TYPE(s64);
READ_TYPE(S64);
READ_TYPE(f32);
READ_TYPE(F32);
READ_TYPE(f64);
READ_TYPE(F64);
#undef READ_TYPE
static inline size_t
io_buffer_data_type_size(ID data_type) {
#define IO_BUFFER_DATA_TYPE_SIZE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return RB_IO_BUFFER_DATA_TYPE_##name##_SIZE;
IO_BUFFER_DATA_TYPE_SIZE(U8)
IO_BUFFER_DATA_TYPE_SIZE(S8)
IO_BUFFER_DATA_TYPE_SIZE(u16)
IO_BUFFER_DATA_TYPE_SIZE(U16)
IO_BUFFER_DATA_TYPE_SIZE(s16)
IO_BUFFER_DATA_TYPE_SIZE(S16)
IO_BUFFER_DATA_TYPE_SIZE(u32)
IO_BUFFER_DATA_TYPE_SIZE(U32)
IO_BUFFER_DATA_TYPE_SIZE(s32)
IO_BUFFER_DATA_TYPE_SIZE(S32)
IO_BUFFER_DATA_TYPE_SIZE(u64)
IO_BUFFER_DATA_TYPE_SIZE(U64)
IO_BUFFER_DATA_TYPE_SIZE(s64)
IO_BUFFER_DATA_TYPE_SIZE(S64)
IO_BUFFER_DATA_TYPE_SIZE(f32)
IO_BUFFER_DATA_TYPE_SIZE(F32)
IO_BUFFER_DATA_TYPE_SIZE(f64)
IO_BUFFER_DATA_TYPE_SIZE(F64)
#undef IO_BUFFER_DATA_TYPE_SIZE
rb_raise(rb_eArgError, "Invalid type name!");
}
/*
* call-seq: get_value(type, offset) -> numeric
* call-seq:
* size_of(data_type) -> byte size
* size_of(array of data_type) -> byte size
*
* Read from buffer a value of +type+ at +offset+. +type+ should be one
* Returns the size of the given data type(s) in bytes.
*
* Example:
*
* IO::Buffer.size_of(:u32) # => 4
* IO::Buffer.size_of([:u32, :u32]) # => 8
*/
static VALUE
io_buffer_size_of(VALUE klass, VALUE data_type)
{
if (RB_TYPE_P(data_type, T_ARRAY)) {
size_t total = 0;
for (long i = 0; i < RARRAY_LEN(data_type); i++) {
total += io_buffer_data_type_size(RB_SYM2ID(RARRAY_AREF(data_type, i)));
}
return SIZET2NUM(total);
} else {
return SIZET2NUM(io_buffer_data_type_size(RB_SYM2ID(data_type)));
}
}
static inline VALUE
rb_io_buffer_get_value(const void* base, size_t size, ID data_type, size_t *offset)
{
#define IO_BUFFER_GET_VALUE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) return io_buffer_read_##name(base, size, offset);
IO_BUFFER_GET_VALUE(U8)
IO_BUFFER_GET_VALUE(S8)
IO_BUFFER_GET_VALUE(u16)
IO_BUFFER_GET_VALUE(U16)
IO_BUFFER_GET_VALUE(s16)
IO_BUFFER_GET_VALUE(S16)
IO_BUFFER_GET_VALUE(u32)
IO_BUFFER_GET_VALUE(U32)
IO_BUFFER_GET_VALUE(s32)
IO_BUFFER_GET_VALUE(S32)
IO_BUFFER_GET_VALUE(u64)
IO_BUFFER_GET_VALUE(U64)
IO_BUFFER_GET_VALUE(s64)
IO_BUFFER_GET_VALUE(S64)
IO_BUFFER_GET_VALUE(f32)
IO_BUFFER_GET_VALUE(F32)
IO_BUFFER_GET_VALUE(f64)
IO_BUFFER_GET_VALUE(F64)
#undef IO_BUFFER_GET_VALUE
rb_raise(rb_eArgError, "Invalid type name!");
}
/*
* call-seq: get_value(data_type, offset) -> numeric
*
* Read from buffer a value of +type+ at +offset+. +data_type+ should be one
* of symbols:
*
* * +:U8+: unsigned integer, 1 byte
@ -1553,13 +1593,16 @@ rb_io_buffer_get_value(const void* base, size_t size, ID type, size_t offset)
* * +:f64+: double, 8 bytes, little-endian
* * +:F64+: double, 8 bytes, big-endian
*
* A data type refers specifically to the type of binary data that is stored
* in the buffer. For example, a +:u32+ data type is a 32-bit unsigned
* integer in little-endian format.
*
* Example:
*
* string = [1.5].pack('f')
* # => "\x00\x00\xC0?"
* IO::Buffer.for(string).get_value(:f32, 0)
* # => 1.5
*
*/
static VALUE
io_buffer_get_value(VALUE self, VALUE type, VALUE _offset)
@ -1570,36 +1613,230 @@ io_buffer_get_value(VALUE self, VALUE type, VALUE _offset)
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
return rb_io_buffer_get_value(base, size, RB_SYM2ID(type), offset);
return rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset);
}
void
rb_io_buffer_set_value(const void* base, size_t size, ID type, size_t offset, VALUE value)
/*
* call-seq: get_values(data_types, offset) -> array
*
* Similar to #get_value, except that it can handle multiple data types and
* returns an array of values.
*
* Example:
*
* string = [1.5, 2.5].pack('ff')
* IO::Buffer.for(string).get_values([:f32, :f32], 0)
* # => [1.5, 2.5]
*/
static VALUE
io_buffer_get_values(VALUE self, VALUE data_types, VALUE _offset)
{
#define WRITE_TYPE(name) if (type == RB_IO_BUFFER_TYPE_##name) {io_buffer_write_##name(base, size, &offset, value); return;}
WRITE_TYPE(U8);
WRITE_TYPE(S8);
size_t offset = NUM2SIZET(_offset);
WRITE_TYPE(u16);
WRITE_TYPE(U16);
WRITE_TYPE(s16);
WRITE_TYPE(S16);
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
WRITE_TYPE(u32);
WRITE_TYPE(U32);
WRITE_TYPE(s32);
WRITE_TYPE(S32);
if (!RB_TYPE_P(data_types, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument data_types should be an array!");
}
WRITE_TYPE(u64);
WRITE_TYPE(U64);
WRITE_TYPE(s64);
WRITE_TYPE(S64);
VALUE array = rb_ary_new_capa(RARRAY_LEN(data_types));
WRITE_TYPE(f32);
WRITE_TYPE(F32);
WRITE_TYPE(f64);
WRITE_TYPE(F64);
#undef WRITE_TYPE
for (long i = 0; i < RARRAY_LEN(data_types); i++) {
VALUE type = rb_ary_entry(data_types, i);
VALUE value = rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset);
rb_ary_push(array, value);
}
return array;
}
/*
* call-seq:
* each(data_type, [offset, [count]]) {|offset, value| ...} -> self
* each(data_type, [offset, [count]]) -> enumerator
*
* Iterates over the buffer, yielding each +value+ of +data_type+ starting
* from +offset+.
*
* If +count+ is given, only +count+ values will be yielded.
*
* Example:
*
* IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value|
* puts "#{offset}: #{value}"
* end
* # 2: 108
* # 3: 108
*/
static VALUE
io_buffer_each(int argc, VALUE *argv, VALUE self)
{
RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
ID data_type;
if (argc >= 1) {
data_type = RB_SYM2ID(argv[0]);
} else {
data_type = RB_IO_BUFFER_DATA_TYPE_U8;
}
size_t offset;
if (argc >= 2) {
offset = NUM2SIZET(argv[1]);
} else {
offset = 0;
}
size_t count;
if (argc >= 3) {
count = NUM2SIZET(argv[2]);
} else {
count = (size - offset) / io_buffer_data_type_size(data_type);
}
for (size_t i = 0; i < count; i++) {
size_t current_offset = offset;
VALUE value = rb_io_buffer_get_value(base, size, data_type, &offset);
rb_yield_values(2, SIZET2NUM(current_offset), value);
}
return self;
}
/*
* call-seq: values(data_type, [offset, [count]]) -> array
*
* Returns an array of values of +data_type+ starting from +offset+.
*
* If +count+ is given, only +count+ values will be returned.
*
* Example:
*
* IO::Buffer.for("Hello World").values(:U8, 2, 2)
* # => [108, 108]
*/
static VALUE
io_buffer_values(int argc, VALUE *argv, VALUE self)
{
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
ID data_type;
if (argc >= 1) {
data_type = RB_SYM2ID(argv[0]);
} else {
data_type = RB_IO_BUFFER_DATA_TYPE_U8;
}
size_t offset;
if (argc >= 2) {
offset = NUM2SIZET(argv[1]);
} else {
offset = 0;
}
size_t count;
if (argc >= 3) {
count = NUM2SIZET(argv[2]);
} else {
count = (size - offset) / io_buffer_data_type_size(data_type);
}
VALUE array = rb_ary_new_capa(count);
for (size_t i = 0; i < count; i++) {
VALUE value = rb_io_buffer_get_value(base, size, data_type, &offset);
rb_ary_push(array, value);
}
return array;
}
/*
* call-seq:
* each_byte([offset, [count]]) {|offset, byte| ...} -> self
* each_byte([offset, [count]]) -> enumerator
*
* Iterates over the buffer, yielding each byte starting from +offset+.
*
* If +count+ is given, only +count+ bytes will be yielded.
*
* Example:
*
* IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte|
* puts "#{offset}: #{byte}"
* end
* # 2: 108
* # 3: 108
*/
static VALUE
io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
{
RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
size_t offset;
if (argc >= 2) {
offset = NUM2SIZET(argv[1]);
} else {
offset = 0;
}
size_t count;
if (argc >= 3) {
count = NUM2SIZET(argv[2]);
} else {
count = (size - offset);
}
for (size_t i = 0; i < count; i++) {
unsigned char *value = (unsigned char *)base + i + offset;
rb_yield(RB_INT2FIX(*value));
}
return self;
}
inline static void
rb_io_buffer_set_value(const void* base, size_t size, ID data_type, size_t *offset, VALUE value)
{
#define IO_BUFFER_SET_VALUE(name) if (data_type == RB_IO_BUFFER_DATA_TYPE_##name) {io_buffer_write_##name(base, size, offset, value); return;}
IO_BUFFER_SET_VALUE(U8);
IO_BUFFER_SET_VALUE(S8);
IO_BUFFER_SET_VALUE(u16);
IO_BUFFER_SET_VALUE(U16);
IO_BUFFER_SET_VALUE(s16);
IO_BUFFER_SET_VALUE(S16);
IO_BUFFER_SET_VALUE(u32);
IO_BUFFER_SET_VALUE(U32);
IO_BUFFER_SET_VALUE(s32);
IO_BUFFER_SET_VALUE(S32);
IO_BUFFER_SET_VALUE(u64);
IO_BUFFER_SET_VALUE(U64);
IO_BUFFER_SET_VALUE(s64);
IO_BUFFER_SET_VALUE(S64);
IO_BUFFER_SET_VALUE(f32);
IO_BUFFER_SET_VALUE(F32);
IO_BUFFER_SET_VALUE(f64);
IO_BUFFER_SET_VALUE(F64);
#undef IO_BUFFER_SET_VALUE
rb_raise(rb_eArgError, "Invalid type name!");
}
@ -1640,7 +1877,53 @@ io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
rb_io_buffer_get_bytes_for_writing(self, &base, &size);
rb_io_buffer_set_value(base, size, RB_SYM2ID(type), offset, value);
rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value);
return SIZET2NUM(offset);
}
/*
* call-seq: set_values(data_types, offset, values) -> offset
*
* Write +values+ of +data_types+ at +offset+ to the buffer. +data_types+
* should be an array of symbols as described in #get_value. +values+ should
* be an array of values to write.
*
* Example:
*
* buffer = IO::Buffer.new(8)
* buffer.set_values([:U8, :U16], 0, [1, 2])
* buffer
* # =>
* # #<IO::Buffer 0x696f717561746978+8 INTERNAL>
* # 0x00000000 01 00 02 00 00 00 00 00 ........
*/
static VALUE
io_buffer_set_values(VALUE self, VALUE data_types, VALUE _offset, VALUE values)
{
if (!RB_TYPE_P(data_types, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument data_types should be an array!");
}
if (!RB_TYPE_P(values, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument values should be an array!");
}
if (RARRAY_LEN(data_types) != RARRAY_LEN(values)) {
rb_raise(rb_eArgError, "Argument data_types and values should have the same length!");
}
size_t offset = NUM2SIZET(_offset);
void *base;
size_t size;
rb_io_buffer_get_bytes_for_writing(self, &base, &size);
for (long i = 0; i < RARRAY_LEN(data_types); i++) {
VALUE type = rb_ary_entry(data_types, i);
VALUE value = rb_ary_entry(values, i);
rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value);
}
return SIZET2NUM(offset);
}
@ -1719,7 +2002,6 @@ io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t s
* # =>
* # #<IO::Buffer 0x0000558cbec03320+11 INTERNAL>
* # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
*
*/
static VALUE
rb_io_buffer_initialize_copy(VALUE self, VALUE source)
@ -1790,7 +2072,6 @@ rb_io_buffer_initialize_copy(VALUE self, VALUE source)
* buffer = IO::Buffer.new(2)
* buffer.copy(IO::Buffer.for('test'), 0)
* # in `copy': Specified offset+length exceeds source size! (ArgumentError)
*
*/
static VALUE
io_buffer_copy(int argc, VALUE *argv, VALUE self)
@ -1822,7 +2103,6 @@ io_buffer_copy(int argc, VALUE *argv, VALUE self)
* # => "st"
* buffer.get_string(2, 1)
* # => "s"
*
*/
static VALUE
io_buffer_get_string(int argc, VALUE *argv, VALUE self)
@ -1944,7 +2224,6 @@ rb_io_buffer_clear(VALUE self, uint8_t value, size_t offset, size_t length)
* # =>
* # <IO::Buffer 0x00007fca40087c38+4 SLICE>
* # 0x00000000 01 02 02 02 ....
*
*/
static VALUE
io_buffer_clear(int argc, VALUE *argv, VALUE self)
@ -2685,17 +2964,41 @@ Init_IO_Buffer(void)
rb_include_module(rb_cIOBuffer, rb_mComparable);
#define DEFINE_TYPE(name) RB_IO_BUFFER_TYPE_##name = rb_intern_const(#name)
DEFINE_TYPE(U8); DEFINE_TYPE(S8);
DEFINE_TYPE(u16); DEFINE_TYPE(U16); DEFINE_TYPE(s16); DEFINE_TYPE(S16);
DEFINE_TYPE(u32); DEFINE_TYPE(U32); DEFINE_TYPE(s32); DEFINE_TYPE(S32);
DEFINE_TYPE(u64); DEFINE_TYPE(U64); DEFINE_TYPE(s64); DEFINE_TYPE(S64);
DEFINE_TYPE(f32); DEFINE_TYPE(F32); DEFINE_TYPE(f64); DEFINE_TYPE(F64);
#undef DEFINE_TYPE
#define IO_BUFFER_DEFINE_DATA_TYPE(name) RB_IO_BUFFER_DATA_TYPE_##name = rb_intern_const(#name)
IO_BUFFER_DEFINE_DATA_TYPE(U8);
IO_BUFFER_DEFINE_DATA_TYPE(S8);
IO_BUFFER_DEFINE_DATA_TYPE(u16);
IO_BUFFER_DEFINE_DATA_TYPE(U16);
IO_BUFFER_DEFINE_DATA_TYPE(s16);
IO_BUFFER_DEFINE_DATA_TYPE(S16);
IO_BUFFER_DEFINE_DATA_TYPE(u32);
IO_BUFFER_DEFINE_DATA_TYPE(U32);
IO_BUFFER_DEFINE_DATA_TYPE(s32);
IO_BUFFER_DEFINE_DATA_TYPE(S32);
IO_BUFFER_DEFINE_DATA_TYPE(u64);
IO_BUFFER_DEFINE_DATA_TYPE(U64);
IO_BUFFER_DEFINE_DATA_TYPE(s64);
IO_BUFFER_DEFINE_DATA_TYPE(S64);
IO_BUFFER_DEFINE_DATA_TYPE(f32);
IO_BUFFER_DEFINE_DATA_TYPE(F32);
IO_BUFFER_DEFINE_DATA_TYPE(f64);
IO_BUFFER_DEFINE_DATA_TYPE(F64);
#undef IO_BUFFER_DEFINE_DATA_TYPE
rb_define_singleton_method(rb_cIOBuffer, "size_of", io_buffer_size_of, 1);
// Data access:
rb_define_method(rb_cIOBuffer, "get_value", io_buffer_get_value, 2);
rb_define_method(rb_cIOBuffer, "get_values", io_buffer_get_values, 2);
rb_define_method(rb_cIOBuffer, "each", io_buffer_each, -1);
rb_define_method(rb_cIOBuffer, "values", io_buffer_values, -1);
rb_define_method(rb_cIOBuffer, "each_byte", io_buffer_each_byte, -1);
rb_define_method(rb_cIOBuffer, "set_value", io_buffer_set_value, 3);
rb_define_method(rb_cIOBuffer, "set_values", io_buffer_set_values, 3);
rb_define_method(rb_cIOBuffer, "copy", io_buffer_copy, -1);

View File

@ -235,17 +235,59 @@ class TestIOBuffer < Test::Unit::TestCase
:F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
}
def test_get_set_primitives
def test_get_set_value
buffer = IO::Buffer.new(128)
RANGES.each do |type, values|
RANGES.each do |data_type, values|
values.each do |value|
buffer.set_value(type, 0, value)
assert_equal value, buffer.get_value(type, 0), "Converting #{value} as #{type}."
buffer.set_value(data_type, 0, value)
assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}."
end
end
end
def test_get_set_values
buffer = IO::Buffer.new(128)
RANGES.each do |data_type, values|
format = [data_type] * values.size
buffer.set_values(format, 0, values)
assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}."
end
end
def test_values
buffer = IO::Buffer.new(128)
RANGES.each do |data_type, values|
format = [data_type] * values.size
buffer.set_values(format, 0, values)
assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}."
end
end
def test_each
buffer = IO::Buffer.new(128)
RANGES.each do |data_type, values|
format = [data_type] * values.size
data_type_size = IO::Buffer.size_of(data_type)
values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]}
buffer.set_values(format, 0, values)
assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}."
end
end
def test_each_byte
string = "The quick brown fox jumped over the lazy dog."
buffer = IO::Buffer.for(string)
assert_equal string.bytes, buffer.each_byte.to_a
end
def test_clear
buffer = IO::Buffer.new(16)
buffer.set_string("Hello World!")