Skip to content

Commit

Permalink
Emulate CRuby and clean up append_as_bytes
Browse files Browse the repository at this point in the history
* Calculate growth size in an initial loop.
* Add equivalent to str_ensure_available_capa for growing strings.
* Refactor for arity-split (just 0 and 1 arg forms added).
  • Loading branch information
headius committed Dec 5, 2024
1 parent d1d6fcb commit 6a8044d
Showing 1 changed file with 55 additions and 17 deletions.
72 changes: 55 additions & 17 deletions core/src/main/java/org/jruby/RubyString.java
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,24 @@ public final void modifyExpand(int length) {
clearCodeRange();
}

/**
* Ensure the backing store belongs to this string and has enough space to add extraLength bytes.
*
* MRI: str_ensure_available_capa
*
* @param context the current thread context
* @param extraLength the extra length needed
*/
public void ensureAvailable(ThreadContext context, int extraLength) {
int realSize = value.getRealSize();

if (realSize > Integer.MAX_VALUE - extraLength) {
throw argumentError(context, "string sizes too big");
}

modifyExpand(realSize + extraLength);
}

// io_set_read_length
public void setReadLength(int length) {
if (size() != length) {
Expand Down Expand Up @@ -5514,30 +5532,50 @@ private IRubyObject rpartitionMismatch(ThreadContext context) {

@JRubyMethod(rest = true)
public IRubyObject append_as_bytes(ThreadContext context, IRubyObject[] args) {
checkFrozen();
modify();
int neededCapacity = 0;
for (var arg : args) neededCapacity += byteCapacityFor(context, arg);
this.ensureAvailable(context, neededCapacity);
for (var arg : args) appendBytes(context, arg);

int length = args.length;
for (int i = 0; i < length; i++) {
var arg = args[i];
return this;
}

if (arg instanceof RubyFixnum fix) {
cat(fix.getIntValue() & 0xff);
} else if (arg instanceof RubyBignum big) {
cat(big.getIntValue() & 0xff);
} else if (arg instanceof RubyString str) {
value.append(str.getByteList());
} else {
throw typeError(context, str(context.runtime, "wrong argument type ", types(context.runtime, arg.getType()), " (expected String or Integer)"));
}
}
@JRubyMethod
public IRubyObject append_as_bytes(ThreadContext context) {
this.ensureAvailable(context, 0);

// FIXME: MRI tries and minimize pain by trying to figure out if it should change cr and this is a quick hammer.
clearCodeRange();
return this;
}

@JRubyMethod
public IRubyObject append_as_bytes(ThreadContext context, IRubyObject arg0) {
ensureAvailable(context, byteCapacityFor(context, arg0));
appendBytes(context, arg0);

return this;
}

private static int byteCapacityFor(ThreadContext context, IRubyObject arg) {
return switch (arg) {
case RubyInteger ignored -> 1;
case RubyString str -> str.getByteList().realSize();
default ->
throw typeError(context, str(context.runtime, "wrong argument type ", types(context.runtime, arg.getType()), " (expected String or Integer)"));
};
}

private void appendBytes(ThreadContext context, IRubyObject arg) {
if (arg instanceof RubyFixnum fix) {
cat(fix.getIntValue() & 0xff);
} else if (arg instanceof RubyBignum big) {
cat(big.getBigIntegerValue().intValue() & 0xff);
} else if (arg instanceof RubyString str) {
value.append(str.getByteList());
} else {
throw runtimeError(context, "BUG: append_as_bytes arguments should have been validated");
}
}

/** rb_str_chop / rb_str_chop_bang
*
*/
Expand Down

0 comments on commit 6a8044d

Please sign in to comment.