mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
[Feature #22185] Add alignment directives to pack templates
This commit is contained in:
1 parent
bb472c530a
commit
e9444aa0da
6 files changed
+199
-9
Notes:
git
2026-07-15 13:39:04 +00:00
No files matched your search
@@ -29,6 +29,9 @@ Note: We're only listing outstanding class updates.
|
||||
|
||||
* `Array#pack` accepts new formats `R` and `r` for unsigned and signed
|
||||
LEB128 encoded integers. [[Feature #21785]]
|
||||
* `Array#pack` accepts new formats `x!` and `@!` to align the current
|
||||
offset to a byte boundary or to the ABI alignment of another
|
||||
directive. [[Feature #22185]]
|
||||
|
||||
* ENV
|
||||
|
||||
@@ -79,6 +82,9 @@ Note: We're only listing outstanding class updates.
|
||||
* `String#unpack` and `String#unpack1` accept a new format `^` that
|
||||
returns the current offset. Useful when combined with variable
|
||||
width formats like LEB128. [[Feature #21796]]
|
||||
* `String#unpack` and `String#unpack1` accept new formats `x!` and
|
||||
`@!` to align the current offset to a byte boundary or to the ABI
|
||||
alignment of another directive. [[Feature #22185]]
|
||||
|
||||
* Symbol
|
||||
|
||||
|
||||
@@ -879,6 +879,32 @@ for one byte in the input or output string.
|
||||
"\x00\x00\x02".unpack("x4C") # Raises ArgumentError
|
||||
```
|
||||
|
||||
- `'x!'` - Align to the given byte boundary or directive alignment;
|
||||
for packing, null fill if necessary. With `buffer:` or `offset:`,
|
||||
alignment is relative to the starting position:
|
||||
|
||||
```ruby
|
||||
[1, 2].pack("C x!4 C") # => "\x01\x00\x00\x00\x02"
|
||||
[1, 2].pack("C x!i i") # Aligns to the native int alignment.
|
||||
```
|
||||
|
||||
For unpacking, skip forward to the aligned offset:
|
||||
|
||||
```ruby
|
||||
"\x01\x00\x00\x00\x02".unpack("C x!4 C") # => [1, 2]
|
||||
```
|
||||
|
||||
- `'@!'` - Align to the given byte boundary or directive alignment
|
||||
from the beginning of the output or input string:
|
||||
|
||||
```ruby
|
||||
buffer = +"z"
|
||||
[1, 2].pack("C @!4 C", buffer: buffer)
|
||||
buffer # => "z\x01\x00\x00\x02"
|
||||
|
||||
"z\x01\x00\x00\x02".unpack("C @!4 C", offset: 1) # => [1, 2]
|
||||
```
|
||||
|
||||
- `'^'` - Only for unpacking; the current position:
|
||||
|
||||
```ruby
|
||||
|
||||
@@ -72,8 +72,12 @@ is_bigendian(void)
|
||||
|
||||
#ifdef NATINT_PACK
|
||||
# define NATINT_LEN(type,len) (natint?(int)sizeof(type):(int)(len))
|
||||
# define NATINT_ALIGN(type,len) (natint?(int)RUBY_ALIGNOF(type):(int)(len))
|
||||
# define USING_NATINT(expr) expr
|
||||
#else
|
||||
# define NATINT_LEN(type,len) ((int)sizeof(type))
|
||||
# define NATINT_ALIGN(type,len) ((int)RUBY_ALIGNOF(type))
|
||||
# define USING_NATINT(expr) /* void */
|
||||
#endif
|
||||
|
||||
typedef union {
|
||||
@@ -218,9 +222,7 @@ pack_modifiers(const char *p, const char *pend, char type, int *natint, int *exp
|
||||
case '_':
|
||||
case '!':
|
||||
if (strchr(natstr, type)) {
|
||||
#ifdef NATINT_PACK
|
||||
*natint = 1;
|
||||
#endif
|
||||
USING_NATINT(*natint = 1);
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
@@ -245,6 +247,91 @@ pack_modifiers(const char *p, const char *pend, char type, int *natint, int *exp
|
||||
return p;
|
||||
}
|
||||
|
||||
#ifndef NATINT_PACK
|
||||
# define pack_alignof(t, n) pack_alignof(t)
|
||||
#endif
|
||||
static size_t
|
||||
pack_alignof(char type, int natint)
|
||||
{
|
||||
switch (type) {
|
||||
case 'c': case 'C':
|
||||
return RUBY_ALIGNOF(char);
|
||||
case 's': case 'S':
|
||||
return NATINT_ALIGN(short, 2);
|
||||
case 'i': case 'I':
|
||||
return RUBY_ALIGNOF(int);
|
||||
case 'l': case 'L':
|
||||
return NATINT_ALIGN(long, 4);
|
||||
case 'q': case 'Q':
|
||||
return RUBY_ALIGNOF(int64_t);
|
||||
case 'j':
|
||||
return RUBY_ALIGNOF(intptr_t);
|
||||
case 'J':
|
||||
return RUBY_ALIGNOF(uintptr_t);
|
||||
case 'n': case 'v':
|
||||
return RUBY_ALIGNOF(uint16_t);
|
||||
case 'N': case 'V':
|
||||
return RUBY_ALIGNOF(uint32_t);
|
||||
case 'f': case 'F': case 'e': case 'g':
|
||||
return RUBY_ALIGNOF(float);
|
||||
case 'd': case 'D': case 'E': case 'G':
|
||||
return RUBY_ALIGNOF(double);
|
||||
case 'p': case 'P':
|
||||
return RUBY_ALIGNOF(char *);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static long
|
||||
pack_align_pad(long pos, long base, size_t alignment)
|
||||
{
|
||||
long offset, mod;
|
||||
|
||||
if (alignment <= 1) return 0;
|
||||
if (alignment > LONG_MAX) rb_raise(rb_eRangeError, "alignment too big");
|
||||
offset = pos - base;
|
||||
mod = offset % (long)alignment;
|
||||
if (mod < 0) mod += alignment;
|
||||
return mod ? (long)alignment - mod : 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
pack_alignment(const char *p, const char *pend, VALUE fmt, size_t *alignment)
|
||||
{
|
||||
char type;
|
||||
int explicit_endian = 0;
|
||||
USING_NATINT(int natint = 0);
|
||||
|
||||
if (p >= pend) {
|
||||
rb_raise(rb_eArgError, "missing alignment");
|
||||
}
|
||||
type = *p++;
|
||||
p = pack_modifiers(p, pend, type, &natint, &explicit_endian);
|
||||
if (explicit_endian) {
|
||||
rb_raise(rb_eArgError, "endian modifier is not allowed for alignment");
|
||||
}
|
||||
*alignment = pack_alignof(type, natint);
|
||||
if (!*alignment) {
|
||||
unknown_directive("alignment", type, fmt);
|
||||
}
|
||||
return (char *)p;
|
||||
}
|
||||
|
||||
static char *
|
||||
pack_alignment_size(const char *p, const char *pend, VALUE fmt, size_t *alignment)
|
||||
{
|
||||
if (p < pend && ISDIGIT(*p)) {
|
||||
errno = 0;
|
||||
*alignment = STRTOUL(p, (char**)&p, 10);
|
||||
if (*alignment <= 0 || errno) {
|
||||
rb_raise(rb_eRangeError, "invalid alignment");
|
||||
}
|
||||
return (char *)p;
|
||||
}
|
||||
return pack_alignment(p, pend, fmt, alignment);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
|
||||
{
|
||||
@@ -254,6 +341,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
|
||||
const char *ptr;
|
||||
int enc_info = 1; /* 0 - BINARY, 1 - US-ASCII, 2 - UTF-8 */
|
||||
int integer_size, bigendian_p;
|
||||
long align_base;
|
||||
|
||||
StringValue(fmt);
|
||||
rb_must_asciicompat(fmt);
|
||||
@@ -271,6 +359,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
|
||||
}
|
||||
|
||||
idx = 0;
|
||||
align_base = RSTRING_LEN(res);
|
||||
|
||||
#define TOO_FEW (rb_raise(rb_eArgError, toofew), 0)
|
||||
#define MORE_ITEM (idx < RARRAY_LEN(ary))
|
||||
@@ -281,15 +370,25 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
|
||||
|
||||
while (p < pend) {
|
||||
int explicit_endian = 0;
|
||||
size_t align = 0;
|
||||
if (RSTRING_END(fmt) != pend) {
|
||||
rb_raise(rb_eRuntimeError, "format string modified");
|
||||
}
|
||||
const char type = *p++; /* get data type */
|
||||
#ifdef NATINT_PACK
|
||||
int natint = 0; /* native integer */
|
||||
#endif
|
||||
USING_NATINT(int natint = 0); /* native integer */
|
||||
|
||||
if (skip_blank(p, type)) continue;
|
||||
|
||||
/* Directives that do not take modifiers. */
|
||||
if ((type == 'x' || type == '@') && p < pend && *p == '!') {
|
||||
p++;
|
||||
p = pack_alignment_size(p, pend, fmt, &align);
|
||||
len = pack_align_pad(RSTRING_LEN(res), type == '@' ? 0 : align_base, align);
|
||||
rb_str_modify_expand(res, len);
|
||||
str_expand_fill(res, '\0', len);
|
||||
continue;
|
||||
}
|
||||
|
||||
p = pack_modifiers(p, pend, type, &natint, &explicit_endian);
|
||||
|
||||
if (*p == '*') { /* set data length */
|
||||
@@ -1005,6 +1104,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, VALUE ofs, enum unpack_mode mode)
|
||||
long len;
|
||||
AVOID_CC_BUG long tmp_len;
|
||||
int signed_p, integer_size, bigendian_p;
|
||||
long align_base;
|
||||
#define UNPACK_PUSH(item) do {\
|
||||
VALUE item_val = (item);\
|
||||
if ((mode) == UNPACK_BLOCK) {\
|
||||
@@ -1031,6 +1131,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, VALUE ofs, enum unpack_mode mode)
|
||||
s = RSTRING_PTR(str);
|
||||
send = s + len;
|
||||
s += offset;
|
||||
align_base = offset;
|
||||
|
||||
p = RSTRING_PTR(fmt);
|
||||
pend = p + RSTRING_LEN(fmt);
|
||||
@@ -1041,12 +1142,24 @@ pack_unpack_internal(VALUE str, VALUE fmt, VALUE ofs, enum unpack_mode mode)
|
||||
while (p < pend) {
|
||||
int explicit_endian = 0;
|
||||
const char type = *p++;
|
||||
#ifdef NATINT_PACK
|
||||
int natint = 0; /* native integer */
|
||||
#endif
|
||||
size_t align = 0;
|
||||
int star = 0;
|
||||
USING_NATINT(int natint = 0); /* native integer */
|
||||
|
||||
if (skip_blank(p, type)) continue;
|
||||
|
||||
/* Directives that do not take modifiers. */
|
||||
if ((type == 'x' || type == '@') && p < pend && *p == '!') {
|
||||
p++;
|
||||
p = pack_alignment_size(p, pend, fmt, &align);
|
||||
len = pack_align_pad(s - RSTRING_PTR(str), type == '@' ? 0 : align_base, align);
|
||||
if (len > send - s) {
|
||||
rb_raise(rb_eArgError, type == '@' ? "@ outside of string" : "x outside of string");
|
||||
}
|
||||
s += len;
|
||||
continue;
|
||||
}
|
||||
|
||||
p = pack_modifiers(p, pend, type, &natint, &explicit_endian);
|
||||
|
||||
if (p >= pend)
|
||||
|
||||
@@ -32,6 +32,22 @@ describe "Array#pack with format 'x'" do
|
||||
[].pack("x*").should == ""
|
||||
[1, 2].pack("Cx*C").should == "\x01\x02"
|
||||
end
|
||||
|
||||
ruby_version_is "4.1" do
|
||||
it "aligns to the given byte boundary with the '!' modifier" do
|
||||
[1, 2].pack("C x!4 C").should == "\x01\x00\x00\x00\x02"
|
||||
end
|
||||
|
||||
it "aligns from the beginning of the output with '@!'" do
|
||||
buffer = +"z"
|
||||
[1, 2].pack("C @!4 C", buffer: buffer)
|
||||
buffer.should == "z\x01\x00\x00\x02"
|
||||
end
|
||||
|
||||
it "aligns to a directive's alignment with the '!' modifier" do
|
||||
[1, 2].pack("C x!i i").should == [1, 2].pack("i< i")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Array#pack with format 'X'" do
|
||||
|
||||
@@ -59,4 +59,18 @@ describe "String#unpack with format 'x'" do
|
||||
it "raises an ArgumentError if the count exceeds the size of the String" do
|
||||
-> { "\x01\x02\x03\x04".unpack("C2x3C") }.should.raise(ArgumentError)
|
||||
end
|
||||
|
||||
ruby_version_is "4.1" do
|
||||
it "aligns to the given byte boundary with the '!' modifier" do
|
||||
"\x01\x00\x00\x00\x02".unpack("C x!4 C").should == [1, 2]
|
||||
end
|
||||
|
||||
it "aligns from the beginning of the String with '@!'" do
|
||||
"z\x01\x00\x00\x02".unpack("C @!4 C", offset: 1).should == [1, 2]
|
||||
end
|
||||
|
||||
it "aligns to a directive's alignment with the '!' modifier" do
|
||||
[1, 2].pack("i< i").unpack("C x!i i").should == [1, 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -939,6 +939,21 @@ EXPECTED
|
||||
assert_equal [nil], "a".unpack("C", offset: 1)
|
||||
end
|
||||
|
||||
def test_pack_alignment
|
||||
assert_equal "\x01\x00\x00\x00\x02".b, [1, 2].pack("C x!4 C")
|
||||
assert_equal [1, 2], "\x01\x00\x00\x00\x02".b.unpack("C x!4 C")
|
||||
|
||||
buffer = +"z"
|
||||
[1, 2].pack("C x!4 C", buffer: buffer)
|
||||
assert_equal "z\x01\x00\x00\x00\x02".b, buffer
|
||||
assert_equal [1, 2], buffer.unpack("C x!4 C", offset: 1)
|
||||
|
||||
buffer = +"z"
|
||||
[1, 2].pack("C @!4 C", buffer: buffer)
|
||||
assert_equal "z\x01\x00\x00\x02".b, buffer
|
||||
assert_equal [1, 2], buffer.unpack("C @!4 C", offset: 1)
|
||||
end
|
||||
|
||||
def test_monkey_pack
|
||||
assert_separately([], <<-'end;')
|
||||
$-w = false
|
||||
|
||||
Reference in new issue
Block a user