Implement `String#upto` in Ruby.

Avoid using `mrb_yield` in C code. The function is not recommended.
Because it doesn't work well with fibers.
parent 8b92ab5c
......@@ -386,4 +386,77 @@ class String
end
lines
end
##
# call-seq:
# str.upto(other_str, exclusive=false) {|s| block } -> str
# str.upto(other_str, exclusive=false) -> an_enumerator
#
# Iterates through successive values, starting at <i>str</i> and
# ending at <i>other_str</i> inclusive, passing each value in turn to
# the block. The <code>String#succ</code> method is used to generate
# each value. If optional second argument exclusive is omitted or is false,
# the last value will be included; otherwise it will be excluded.
#
# If no block is given, an enumerator is returned instead.
#
# "a8".upto("b6") {|s| print s, ' ' }
# for s in "a8".."b6"
# print s, ' '
# end
#
# <em>produces:</em>
#
# a8 a9 b0 b1 b2 b3 b4 b5 b6
# a8 a9 b0 b1 b2 b3 b4 b5 b6
#
# If <i>str</i> and <i>other_str</i> contains only ascii numeric characters,
# both are recognized as decimal numbers. In addition, the width of
# string (e.g. leading zeros) is handled appropriately.
#
# "9".upto("11").to_a #=> ["9", "10", "11"]
# "25".upto("5").to_a #=> []
# "07".upto("11").to_a #=> ["07", "08", "09", "10", "11"]
def upto(max, exclusive=false, &block)
return to_enum(:upto, max, exclusive) unless block
raise TypeError, "no implicit conversion of #{max.class} into String" unless max.kind_of? String
len = self.length
maxlen = max.length
# single character
if len == 1 and maxlen == 1
c = self.ord
e = max.ord
while c <= e
break if exclusive and c == e
yield c.chr
c += 1
end
return self
end
# both edges are all digits
bi = self.to_i(10)
ei = max.to_i(10)
len = self.length
if (bi > 0 or bi == "0"*len) and (ei > 0 or ei == "0"*maxlen)
while bi <= ei
break if exclusive and bi == ei
s = bi.to_s
s = s.rjust(len, "0") if s.length < len
yield s
bi += 1
end
return self
end
bs = self
while true
n = (bs <=> max)
break if n > 0
break if exclusive and n == 0
yield bs
break if n == 0
bs = bs.succ
end
self
end
end
......@@ -470,135 +470,6 @@ mrb_str_ord(mrb_state* mrb, mrb_value str)
}
#endif
static mrb_bool
all_digits_p(const char *s, mrb_int len)
{
while (len-- > 0) {
if (!ISDIGIT(*s)) return FALSE;
s++;
}
return TRUE;
}
/*
* call-seq:
* str.upto(other_str, exclusive=false) {|s| block } -> str
* str.upto(other_str, exclusive=false) -> an_enumerator
*
* Iterates through successive values, starting at <i>str</i> and
* ending at <i>other_str</i> inclusive, passing each value in turn to
* the block. The <code>String#succ</code> method is used to generate
* each value. If optional second argument exclusive is omitted or is false,
* the last value will be included; otherwise it will be excluded.
*
* If no block is given, an enumerator is returned instead.
*
* "a8".upto("b6") {|s| print s, ' ' }
* for s in "a8".."b6"
* print s, ' '
* end
*
* <em>produces:</em>
*
* a8 a9 b0 b1 b2 b3 b4 b5 b6
* a8 a9 b0 b1 b2 b3 b4 b5 b6
*
* If <i>str</i> and <i>other_str</i> contains only ascii numeric characters,
* both are recognized as decimal numbers. In addition, the width of
* string (e.g. leading zeros) is handled appropriately.
*
* "9".upto("11").to_a #=> ["9", "10", "11"]
* "25".upto("5").to_a #=> []
* "07".upto("11").to_a #=> ["07", "08", "09", "10", "11"]
*/
static mrb_value
mrb_str_upto(mrb_state *mrb, mrb_value beg)
{
mrb_value end;
mrb_value exclusive = mrb_false_value();
mrb_value block = mrb_nil_value();
mrb_value current, after_end;
mrb_int n;
mrb_bool excl;
mrb_get_args(mrb, "o|o&", &end, &exclusive, &block);
if (mrb_nil_p(block)) {
return mrb_funcall(mrb, beg, "to_enum", 3, mrb_symbol_value(mrb_intern_lit(mrb, "upto")), end, exclusive);
}
end = mrb_string_type(mrb, end);
excl = mrb_test(exclusive);
/* single character */
if (RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1 &&
ISASCII(RSTRING_PTR(beg)[0]) && ISASCII(RSTRING_PTR(end)[0])) {
char c = RSTRING_PTR(beg)[0];
char e = RSTRING_PTR(end)[0];
int ai = mrb_gc_arena_save(mrb);
if (c > e || (excl && c == e)) return beg;
for (;;) {
mrb_yield(mrb, block, mrb_str_new(mrb, &c, 1));
mrb_gc_arena_restore(mrb, ai);
if (!excl && c == e) break;
c++;
if (excl && c == e) break;
}
return beg;
}
/* both edges are all digits */
if (ISDIGIT(RSTRING_PTR(beg)[0]) && ISDIGIT(RSTRING_PTR(end)[0]) &&
all_digits_p(RSTRING_PTR(beg), RSTRING_LEN(beg)) &&
all_digits_p(RSTRING_PTR(end), RSTRING_LEN(end))) {
mrb_int min_width = RSTRING_LEN(beg);
mrb_int bi = mrb_int(mrb, mrb_str_to_inum(mrb, beg, 10, FALSE));
mrb_int ei = mrb_int(mrb, mrb_str_to_inum(mrb, end, 10, FALSE));
int ai = mrb_gc_arena_save(mrb);
while (bi <= ei) {
mrb_value ns, str;
if (excl && bi == ei) break;
ns = mrb_format(mrb, "%S", mrb_fixnum_value(bi));
if (min_width > RSTRING_LEN(ns)) {
str = mrb_str_new(mrb, NULL, min_width);
memset(RSTRING_PTR(str), '0', min_width-RSTRING_LEN(ns));
memcpy(RSTRING_PTR(str)+min_width-RSTRING_LEN(ns),
RSTRING_PTR(ns), RSTRING_LEN(ns));
}
else {
str = ns;
}
mrb_yield(mrb, block, str);
mrb_gc_arena_restore(mrb, ai);
bi++;
}
return beg;
}
/* normal case */
n = mrb_int(mrb, mrb_funcall(mrb, beg, "<=>", 1, end));
if (n > 0 || (excl && n == 0)) return beg;
after_end = mrb_funcall(mrb, end, "succ", 0);
current = mrb_str_dup(mrb, beg);
while (!mrb_str_equal(mrb, current, after_end)) {
int ai = mrb_gc_arena_save(mrb);
mrb_value next = mrb_nil_value();
if (excl || !mrb_str_equal(mrb, current, end))
next = mrb_funcall(mrb, current, "succ", 0);
mrb_yield(mrb, block, current);
if (mrb_nil_p(next)) break;
current = mrb_str_to_str(mrb, next);
if (excl && mrb_str_equal(mrb, current, end)) break;
if (RSTRING_LEN(current) > RSTRING_LEN(end) || RSTRING_LEN(current) == 0)
break;
mrb_gc_arena_restore(mrb, ai);
}
return beg;
}
/*
* call-seq:
* str.delete_prefix!(prefix) -> self or nil
......@@ -759,7 +630,6 @@ mrb_mruby_string_ext_gem_init(mrb_state* mrb)
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());
mrb_define_method(mrb, s, "upto", mrb_str_upto, MRB_ARGS_ANY());
mrb_define_method(mrb, s, "delete_prefix!", mrb_str_del_prefix_bang, MRB_ARGS_REQ(1));
mrb_define_method(mrb, s, "delete_prefix", mrb_str_del_prefix, MRB_ARGS_REQ(1));
mrb_define_method(mrb, s, "delete_suffix!", mrb_str_del_suffix_bang, MRB_ARGS_REQ(1));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment