Skip to content

Commit

Permalink
String#ljust and String#rjust reimplementation (fix #3445)
Browse files Browse the repository at this point in the history
 - String#ljust and String#rjust are now C functions to improve performance
 - infinite loop because of an empty padding argument is now prevented (ArgumentError is raised)
 - extra tests for ljust/rjust added
  • Loading branch information
dabroz committed Feb 10, 2017
1 parent d0ecf86 commit 981105b
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 46 deletions.
46 changes: 0 additions & 46 deletions mrbgems/mruby-string-ext/mrblib/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,52 +263,6 @@ def insert(idx, str)
self
end

##
# call-seq:
# str.ljust(integer, padstr=' ') -> new_str
#
# If <i>integer</i> is greater than the length of <i>str</i>, returns a new
# <code>String</code> of length <i>integer</i> with <i>str</i> left justified
# and padded with <i>padstr</i>; otherwise, returns <i>str</i>.
#
# "hello".ljust(4) #=> "hello"
# "hello".ljust(20) #=> "hello "
# "hello".ljust(20, '1234') #=> "hello123412341234123"
def ljust(idx, padstr = ' ')
if idx <= self.size
return self
end
newstr = self.dup
newstr << padstr
while newstr.size <= idx
newstr << padstr
end
return newstr.slice(0,idx)
end

##
# call-seq:
# str.rjust(integer, padstr=' ') -> new_str
#
# If <i>integer</i> is greater than the length of <i>str</i>, returns a new
# <code>String</code> of length <i>integer</i> with <i>str</i> right justified
# and padded with <i>padstr</i>; otherwise, returns <i>str</i>.
#
# "hello".rjust(4) #=> "hello"
# "hello".rjust(20) #=> " hello"
# "hello".rjust(20, '1234') #=> "123412341234123hello"
def rjust(idx, padstr = ' ')
if idx <= self.size
return self
end
padsize = idx - self.size
newstr = padstr.dup
while newstr.size <= padsize
newstr << padstr
end
return newstr.slice(0,padsize) + self
end

# str.upto(other_str, exclusive=false) {|s| block } -> str
# str.upto(other_str, exclusive=false) -> an_enumerator
#
Expand Down
92 changes: 92 additions & 0 deletions mrbgems/mruby-string-ext/src/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include <mruby/string.h>
#include <mruby/range.h>

#define MAX(a,b) ((a)>(b) ? (a) : (b))
#define MIN(a,b) ((a)<(b) ? (a) : (b))

static mrb_value
mrb_str_getbyte(mrb_state *mrb, mrb_value str)
{
Expand Down Expand Up @@ -509,6 +512,93 @@ mrb_str_ord(mrb_state* mrb, mrb_value str)
}
#endif

static mrb_value
mrb_str_just(mrb_state* mrb, mrb_value str, mrb_bool right_just)
{
mrb_value new_str;
mrb_int idx = 0, i = 0, bytes_to_copy = 0, start_pos = 0, final_pos = 0,
pad_str_length = 0;
mrb_int str_length = RSTRING_LEN(str);
const char *pad_str = NULL;
char *new_str_ptr = NULL;

mrb_get_args(mrb, "i|s!", &idx, &pad_str, &pad_str_length);

if (pad_str == NULL)
{
pad_str = " ";
pad_str_length = 1;
}

if (pad_str_length == 0)
{
mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding");
}

if (idx <= str_length)
{
return str;
}

new_str = mrb_str_dup(mrb, str);
mrb_str_resize(mrb, new_str, idx);

new_str_ptr = RSTRING_PTR(new_str);

if (right_just)
{
memcpy(new_str_ptr + idx - str_length, RSTRING_PTR(str), str_length);
}

start_pos = right_just ? 0 : str_length;
final_pos = idx - (right_just ? str_length : 0);

for (i = start_pos; i < final_pos; i += pad_str_length)
{
bytes_to_copy = idx - i - (right_just ? str_length : 0);
bytes_to_copy = MIN(pad_str_length, bytes_to_copy);
memcpy(new_str_ptr + i, pad_str, bytes_to_copy);
}

return new_str;
}

/*
* call-seq:
* str.ljust(integer, padstr=' ') -> new_str
*
* If <i>integer</i> is greater than the length of <i>str</i>, returns a new
* <code>String</code> of length <i>integer</i> with <i>str</i> left justified
* and padded with <i>padstr</i>; otherwise, returns <i>str</i>.
*
* "hello".ljust(4) #=> "hello"
* "hello".ljust(20) #=> "hello "
* "hello".ljust(20, '1234') #=> "hello123412341234123"
*/
static mrb_value
mrb_str_ljust(mrb_state* mrb, mrb_value str)
{
return mrb_str_just(mrb, str, FALSE);
}

/*
* call-seq:
* str.rjust(integer, padstr=' ') -> new_str
*
* If <i>integer</i> is greater than the length of <i>str</i>, returns a new
* <code>String</code> of length <i>integer</i> with <i>str</i> right justified
* and padded with <i>padstr</i>; otherwise, returns <i>str</i>.
*
* "hello".rjust(4) #=> "hello"
* "hello".rjust(20) #=> " hello"
* "hello".rjust(20, '1234') #=> "123412341234123hello"
*/
static mrb_value
mrb_str_rjust(mrb_state* mrb, mrb_value str)
{
return mrb_str_just(mrb, str, TRUE);
}

void
mrb_mruby_string_ext_gem_init(mrb_state* mrb)
{
Expand All @@ -530,6 +620,8 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb)
mrb_define_method(mrb, s, "lines", mrb_str_lines, MRB_ARGS_NONE());
mrb_define_method(mrb, s, "succ", mrb_str_succ, MRB_ARGS_NONE());
mrb_define_method(mrb, s, "succ!", mrb_str_succ_bang, MRB_ARGS_NONE());
mrb_define_method(mrb, s, "ljust", mrb_str_ljust, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
mrb_define_method(mrb, s, "rjust", mrb_str_rjust, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1));
mrb_alias_method(mrb, s, mrb_intern_lit(mrb, "next"), mrb_intern_lit(mrb, "succ"));
mrb_alias_method(mrb, s, mrb_intern_lit(mrb, "next!"), mrb_intern_lit(mrb, "succ!"));
mrb_define_method(mrb, s, "ord", mrb_str_ord, MRB_ARGS_NONE());
Expand Down
20 changes: 20 additions & 0 deletions mrbgems/mruby-string-ext/test/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,26 @@ def o.to_str
assert_equal "hello", "hello".rjust(-3)
end

assert('String#ljust should not change string') do
a = "hello"
a.ljust(20)
assert_equal "hello", a
end

assert('String#rjust should not change string') do
a = "hello"
a.rjust(20)
assert_equal "hello", a
end

assert('String#ljust should raise on zero width padding') do
assert_raise(ArgumentError) { "foo".ljust(10, '') }
end

assert('String#rjust should raise on zero width padding') do
assert_raise(ArgumentError) { "foo".rjust(10, '') }
end

assert('String#upto') do
a = "aa"
start = "aa"
Expand Down

0 comments on commit 981105b

Please sign in to comment.