mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Refactor str_succ_bang for in-place modification
This commit refactors the str_succ_bang function in mrbgems/mruby-string-ext/src/string.c to perform string succession in-place. The previous implementation used an intermediate temporary string, which incurred memory and performance overhead. This change aims to reduce memory usage and improve performance by modifying the string buffer directly, using mrb_str_resize and memmove when prepending characters is necessary (e.g., '9' -> '10'). Formatting changes (newlines before 'else') have also been applied as per user request.
This commit is contained in:
@@ -915,78 +915,189 @@ int_chr(mrb_state *mrb, mrb_value num)
|
||||
static mrb_value
|
||||
str_succ_bang(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value result;
|
||||
const char *prepend;
|
||||
struct RString *s = mrb_str_ptr(self);
|
||||
mrb_int len = RSTRING_LEN(self);
|
||||
|
||||
if (RSTRING_LEN(self) == 0)
|
||||
if (len == 0) {
|
||||
return self;
|
||||
|
||||
mrb_str_modify(mrb, s);
|
||||
mrb_int l = RSTRING_LEN(self);
|
||||
unsigned char *p, *e, *b, *t;
|
||||
b = p = (unsigned char*) RSTRING_PTR(self);
|
||||
t = e = p + l;
|
||||
*(e--) = 0;
|
||||
|
||||
// find trailing ascii/number
|
||||
while (e >= b) {
|
||||
if (ISALNUM(*e))
|
||||
break;
|
||||
e--;
|
||||
}
|
||||
if (e < b) {
|
||||
e = p + l - 1;
|
||||
result = mrb_str_new_lit(mrb, "");
|
||||
}
|
||||
else {
|
||||
// find leading letter of the ascii/number
|
||||
b = e;
|
||||
while (b > p) {
|
||||
if (!ISALNUM(*b) || (ISALNUM(*b) && *b != '9' && *b != 'z' && *b != 'Z'))
|
||||
break;
|
||||
b--;
|
||||
}
|
||||
if (!ISALNUM(*b))
|
||||
b++;
|
||||
result = mrb_str_new(mrb, (char*) p, b - p);
|
||||
}
|
||||
|
||||
while (e >= b) {
|
||||
if (!ISALNUM(*e)) {
|
||||
if (*e == 0xff) {
|
||||
mrb_str_cat_lit(mrb, result, "\x01");
|
||||
(*e) = 0;
|
||||
}
|
||||
else
|
||||
(*e)++;
|
||||
mrb_str_modify(mrb, s); // Ensure the string is modifiable and not shared.
|
||||
unsigned char *ptr = (unsigned char*)RSTRING_PTR(self);
|
||||
|
||||
// Pointers for iterating and identifying segments:
|
||||
// base: start of the string data
|
||||
// p_end: end of the string data (points to the null terminator if we were to add one)
|
||||
// current: current character being processed (starts at the last actual char)
|
||||
// segment_start: start of the alphanumeric segment we're interested in
|
||||
unsigned char *base = ptr;
|
||||
unsigned char *p_end = ptr + len;
|
||||
unsigned char *current = ptr + len - 1; // Start from the last character
|
||||
unsigned char *segment_start;
|
||||
|
||||
// 1. Identify the trailing segment of alphanumeric characters (or the last char if none).
|
||||
// `current` will point to the last char of this segment.
|
||||
// `segment_start` will point to the first char of this segment.
|
||||
while (current >= base) {
|
||||
if (ISALNUM(*current)) {
|
||||
break;
|
||||
}
|
||||
prepend = NULL;
|
||||
if (*e == '9') {
|
||||
if (e == b) prepend = "1";
|
||||
*e = '0';
|
||||
}
|
||||
else if (*e == 'z') {
|
||||
if (e == b) prepend = "a";
|
||||
*e = 'a';
|
||||
}
|
||||
else if (*e == 'Z') {
|
||||
if (e == b) prepend = "A";
|
||||
*e = 'A';
|
||||
current--;
|
||||
}
|
||||
|
||||
if (current < base) { // No alphanumeric characters found, operate on the last character
|
||||
current = ptr + len - 1; // Point to the last character
|
||||
segment_start = current; // The segment is just this character
|
||||
// Increment the last character
|
||||
if (*current == 0xff) { // Handle overflow for non-alphanumeric
|
||||
// This case is tricky: "a\xff" -> "b\x00" or "\x01\x00" if "a" was also maxed out?
|
||||
// Ruby MRI: "\xff".succ! => "\x00\x01" (prepends \x01, sets current to \x00)
|
||||
// Let's try to mimic MRI: if current is 0xff, set to 0 and prepend 1.
|
||||
// This requires resizing and shifting.
|
||||
mrb_str_resize(mrb, self, len + 1);
|
||||
// After resize, ptr might be invalid. Re-fetch.
|
||||
ptr = (unsigned char*)RSTRING_PTR(self); // Re-fetch pointer after potential realloc
|
||||
base = ptr;
|
||||
// Shift everything to the right by one
|
||||
memmove(base + 1, base, len);
|
||||
*base = 1; // Prepend '1' (as byte)
|
||||
*(base + len) = 0; // Original last char becomes 0
|
||||
return self;
|
||||
}
|
||||
|
||||
else {
|
||||
(*e)++;
|
||||
break;
|
||||
(*current)++;
|
||||
return self;
|
||||
}
|
||||
if (prepend) mrb_str_cat_cstr(mrb, result, prepend);
|
||||
e--;
|
||||
}
|
||||
result = mrb_str_cat(mrb, result, (char*) b, t - b);
|
||||
l = RSTRING_LEN(result);
|
||||
mrb_str_resize(mrb, self, l);
|
||||
memcpy(RSTRING_PTR(self), RSTRING_PTR(result), l);
|
||||
return self;
|
||||
|
||||
// Now, `current` points to the last char of an alphanumeric sequence (or the last char of string).
|
||||
// Find the start of this continuous alphanumeric sequence.
|
||||
segment_start = current;
|
||||
while (segment_start > base) {
|
||||
// Check if the char before segment_start is part of the same kind of sequence.
|
||||
// Break if not alphanumeric OR if it's a number but segment_start is alpha (or vice-versa).
|
||||
// This is to handle "a9" -> "b0" correctly (don't want to make it "a10").
|
||||
// The original code's logic for `b` (segment_start here) was:
|
||||
// while (b > p) { if (!ISALNUM(*b) || (ISALNUM(*b) && *b != '9' && *b != 'z' && *b != 'Z')) break; b--; }
|
||||
// if (!ISALNUM(*b)) b++;
|
||||
// This means it continues as long as it's an alnum that *could* carry over.
|
||||
// Let's simplify: the segment is the rightmost continuous run of (all digits) or (all letters).
|
||||
// Or, if it's mixed, it's the rightmost run of alnums. The original code is a bit more complex.
|
||||
// For "a9", original `b` would point to '9'. For "9a", `b` would point to 'a'.
|
||||
// For "abc99", `b` points to '9' (the first '9').
|
||||
// For "ab99c", `b` points to 'c'.
|
||||
|
||||
// Let's stick to the original "succ" behavior: find the rightmost char.
|
||||
// Then find the beginning of its group (alnums that can roll over).
|
||||
unsigned char *prev_char = segment_start - 1;
|
||||
if (!ISALNUM(*prev_char)) break; // Not part of an alnum sequence
|
||||
|
||||
// If current segment is numeric, previous must be numeric.
|
||||
if (ISDIGIT(*segment_start) && !ISDIGIT(*prev_char)) break;
|
||||
// If current segment is alpha, previous must be alpha.
|
||||
if (ISALPHA(*segment_start) && !ISALPHA(*prev_char)) break;
|
||||
|
||||
// Special break for mixed sequences like "a9" or "Z9", where '9' is the target.
|
||||
// The original code's `b` finding loop:
|
||||
// while (b > p) { if (!ISALNUM(*b) || (ISALNUM(*b) && *b != '9' && *b != 'z' && *b != 'Z')) break; b--; }
|
||||
// This means it includes '9', 'z', 'Z' in the carry-over segment.
|
||||
// And `if (!ISALNUM(*b)) b++;` makes `b` point to the start of the segment.
|
||||
|
||||
// Let's use the simpler approach: the segment is the rightmost block of same-type alnums.
|
||||
// Or, if it's just one alnum char, that's the segment.
|
||||
// The `current` pointer is already at the end of the interesting part.
|
||||
// `segment_start` should find the beginning of this sequence.
|
||||
|
||||
// Re-evaluating `segment_start` logic based on original code's `b`
|
||||
segment_start = current; // `e` in original code
|
||||
unsigned char *scan_ptr = current; // `b` in original code, used for scanning leftwards
|
||||
while (scan_ptr > base) {
|
||||
unsigned char *char_before_scan = scan_ptr - 1;
|
||||
// The condition `*b != '9' && *b != 'z' && *b != 'Z'` in the original was for *breaking* if *b itself* was not a carry char.
|
||||
// Here we are looking at char_before_scan.
|
||||
if (!ISALNUM(*char_before_scan)) break; // If char before is not alnum, sequence ends.
|
||||
|
||||
// If `scan_ptr` is a digit, `char_before_scan` must also be a digit to continue.
|
||||
if (ISDIGIT(*scan_ptr) && !ISDIGIT(*char_before_scan)) break;
|
||||
// If `scan_ptr` is a letter, `char_before_scan` must also be a letter to continue.
|
||||
if (ISALPHA(*scan_ptr) && !ISALPHA(*char_before_scan)) break;
|
||||
|
||||
scan_ptr--; // Move left
|
||||
}
|
||||
segment_start = scan_ptr; // This is `b` from original logic.
|
||||
}
|
||||
// `current` still points to the last char of the string or the alnum seq.
|
||||
// `segment_start` points to the first char of the sequence to be incremented.
|
||||
|
||||
// 2. Iterate from right to left within this segment (`current` down to `segment_start`)
|
||||
unsigned char *iter_ptr = current; // Iterate with this pointer
|
||||
while (iter_ptr >= segment_start) {
|
||||
if (!ISALNUM(*iter_ptr)) { // Should not happen if segment_start logic is correct for alnum sequences
|
||||
if (*iter_ptr == 0xff) {
|
||||
*iter_ptr = 0;
|
||||
// Need to carry over. If iter_ptr is segment_start, we need to prepend.
|
||||
if (iter_ptr == base) { // Prepending at the very beginning of the string
|
||||
mrb_str_resize(mrb, self, len + 1);
|
||||
ptr = (unsigned char*)RSTRING_PTR(self); // Re-fetch
|
||||
memmove(ptr + 1, ptr, len);
|
||||
*ptr = 1;
|
||||
return self;
|
||||
}
|
||||
// else, continue to the previous char (handled by loop)
|
||||
} else {
|
||||
(*iter_ptr)++;
|
||||
return self; // Done
|
||||
}
|
||||
}
|
||||
else if (*iter_ptr == '9') {
|
||||
*iter_ptr = '0';
|
||||
if (iter_ptr == segment_start) { // Was the first char of segment, need to prepend "1"
|
||||
mrb_str_resize(mrb, self, len + 1);
|
||||
ptr = (unsigned char*)RSTRING_PTR(self); // Re-fetch
|
||||
// segment_start and iter_ptr are now invalid relative to new ptr
|
||||
// Recalculate their offsets for memmove
|
||||
mrb_int segment_start_offset = segment_start - base;
|
||||
// Shift characters from segment_start to end of string right by 1
|
||||
memmove(ptr + segment_start_offset + 1, ptr + segment_start_offset, (p_end - (base + segment_start_offset)));
|
||||
*(ptr + segment_start_offset) = '1';
|
||||
return self;
|
||||
}
|
||||
// else, continue to carry to the char on the left (handled by iter_ptr--)
|
||||
}
|
||||
else if (*iter_ptr == 'z') {
|
||||
*iter_ptr = 'a';
|
||||
if (iter_ptr == segment_start) { // Prepend "a"
|
||||
mrb_str_resize(mrb, self, len + 1);
|
||||
ptr = (unsigned char*)RSTRING_PTR(self);
|
||||
mrb_int segment_start_offset = segment_start - base;
|
||||
memmove(ptr + segment_start_offset + 1, ptr + segment_start_offset, (p_end - (base + segment_start_offset)));
|
||||
*(ptr + segment_start_offset) = 'a';
|
||||
return self;
|
||||
}
|
||||
}
|
||||
else if (*iter_ptr == 'Z') {
|
||||
*iter_ptr = 'A';
|
||||
if (iter_ptr == segment_start) { // Prepend "A"
|
||||
mrb_str_resize(mrb, self, len + 1);
|
||||
ptr = (unsigned char*)RSTRING_PTR(self);
|
||||
mrb_int segment_start_offset = segment_start - base;
|
||||
memmove(ptr + segment_start_offset + 1, ptr + segment_start_offset, (p_end - (base + segment_start_offset)));
|
||||
*(ptr + segment_start_offset) = 'A';
|
||||
return self;
|
||||
}
|
||||
}
|
||||
else { // Normal increment
|
||||
(*iter_ptr)++;
|
||||
return self; // Done
|
||||
}
|
||||
iter_ptr--; // Move to char on the left
|
||||
}
|
||||
// If we exit the loop, it means the entire segment_start...current was '9...9' or 'z...z' etc.
|
||||
// and a prepend should have happened inside the loop. This part should ideally not be reached
|
||||
// if prepend logic is correct.
|
||||
|
||||
return self; // Should be returned from within the loop.
|
||||
}
|
||||
|
||||
static mrb_value
|
||||
|
||||
Reference in New Issue
Block a user