/* ** range.c - Range class ** ** See Copyright Notice in mruby.h */ #include "mruby.h" #include "mruby/class.h" #include "mruby/range.h" #include "mruby/string.h" #include #ifndef OTHER #define OTHER 2 #endif #define RANGE_CLASS (mrb_class_obj_get(mrb, "Range")) static void range_check(mrb_state *mrb, mrb_value a, mrb_value b) { mrb_value ans; int ta; int tb; ta = mrb_type(a); tb = mrb_type(b); if ((ta == MRB_TT_FIXNUM || ta == MRB_TT_FLOAT) && (tb == MRB_TT_FIXNUM || tb == MRB_TT_FLOAT)) { return; } ans = mrb_funcall(mrb, a, "<=>", 1, b); if (mrb_nil_p(ans)) { /* can not be compared */ mrb_raise(mrb, E_ARGUMENT_ERROR, "bad value for range"); } } mrb_value mrb_range_new(mrb_state *mrb, mrb_value beg, mrb_value end, int excl) { struct RRange *r; r = (struct RRange*)mrb_obj_alloc(mrb, MRB_TT_RANGE, RANGE_CLASS); r->edges = (struct mrb_range_edges *)mrb_malloc(mrb, sizeof(struct mrb_range_edges)); range_check(mrb, beg, end); r->edges->beg = beg; r->edges->end = end; r->excl = excl; return mrb_range_value(r); } /* * call-seq: * rng.first => obj * rng.begin => obj * * Returns the first object in rng. */ mrb_value mrb_range_beg(mrb_state *mrb, mrb_value range) { struct RRange *r = mrb_range_ptr(range); return r->edges->beg; } /* * call-seq: * rng.end => obj * rng.last => obj * * Returns the object that defines the end of rng. * * (1..10).end #=> 10 * (1...10).end #=> 10 */ mrb_value mrb_range_end(mrb_state *mrb, mrb_value range) { struct RRange *r = mrb_range_ptr(range); return r->edges->end; } /* * call-seq: * range.exclude_end? => true or false * * Returns true if range excludes its end value. */ mrb_value mrb_range_excl(mrb_state *mrb, mrb_value range) { struct RRange *r = mrb_range_ptr(range); return r->excl ? mrb_true_value() : mrb_false_value(); } static void range_init(mrb_state *mrb, mrb_value range, mrb_value beg, mrb_value end, mrb_int exclude_end) { struct RRange *r = mrb_range_ptr(range); range_check(mrb, beg, end); r->excl = exclude_end; r->edges->beg = beg; r->edges->end = end; } /* * call-seq: * Range.new(start, end, exclusive=false) => range * * Constructs a range using the given start and end. If the third * parameter is omitted or is false, the range will include * the end object; otherwise, it will be excluded. */ mrb_value mrb_range_initialize(mrb_state *mrb, mrb_value range) { mrb_value beg, end; mrb_value flags; mrb_get_args(mrb, "ooo", &beg, &end, &flags); /* Ranges are immutable, so that they should be initialized only once. */ range_init(mrb, range, beg, end, mrb_test(flags)); return range; } /* * call-seq: * range == obj => true or false * * Returns true only if * 1) obj is a Range, * 2) obj has equivalent beginning and end items (by comparing them with ==), * 3) obj has the same #exclude_end? setting as rng. * * (0..2) == (0..2) #=> true * (0..2) == Range.new(0,2) #=> true * (0..2) == (0...2) #=> false * */ mrb_value mrb_range_eq(mrb_state *mrb, mrb_value range) { struct RRange *rr; struct RRange *ro; mrb_value obj; mrb_get_args(mrb, "o", &obj); if (mrb_obj_equal(mrb, range, obj)) return mrb_true_value(); /* same class? */ if (!mrb_obj_is_instance_of(mrb, obj, mrb_obj_class(mrb, range))) return mrb_false_value(); rr = mrb_range_ptr(range); ro = mrb_range_ptr(obj); if (!mrb_obj_equal(mrb, rr->edges->beg, ro->edges->beg)) return mrb_false_value(); if (!mrb_obj_equal(mrb, rr->edges->end, ro->edges->end)) return mrb_false_value(); if (rr->excl != ro->excl) return mrb_false_value(); return mrb_true_value(); } static int r_le(mrb_state *mrb, mrb_value a, mrb_value b) { mrb_value r = mrb_funcall(mrb, a, "<=>", 1, b); /* compare result */ /* output :a < b => -1, a = b => 0, a > b => +1 */ if (mrb_type(r) == MRB_TT_FIXNUM) { int c = mrb_fixnum(r); if (c == 0 || c == -1) return TRUE; } return FALSE; } static int r_gt(mrb_state *mrb, mrb_value a, mrb_value b) { mrb_value r = mrb_funcall(mrb, a, "<=>", 1, b); /* output :a < b => -1, a = b => 0, a > b => +1 */ if (mrb_type(r) == MRB_TT_FIXNUM) { if (mrb_fixnum(r) == 1) return TRUE; } return FALSE; } static int r_ge(mrb_state *mrb, mrb_value a, mrb_value b) { mrb_value r = mrb_funcall(mrb, a, "<=>", 1, b); /* compare result */ /* output :a < b => -1, a = b => 0, a > b => +1 */ if (mrb_type(r) == MRB_TT_FIXNUM) { int c = mrb_fixnum(r); if (c == 0 || c == 1) return TRUE; } return FALSE; } /* * call-seq: * range === obj => true or false * range.member?(val) => true or false * range.include?(val) => true or false * */ mrb_value mrb_range_include(mrb_state *mrb, mrb_value range) { mrb_value val; struct RRange *r = mrb_range_ptr(range); mrb_value beg, end; mrb_get_args(mrb, "o", &val); beg = r->edges->beg; end = r->edges->end; if (r_le(mrb, beg, val)) { /* beg <= val */ if (r->excl) { if (r_gt(mrb, end, val)) return mrb_true_value(); /* end > val */ } else { if (r_ge(mrb, end, val)) return mrb_true_value(); /* end >= val */ } } return mrb_false_value(); } /* * call-seq: * rng.each {| i | block } => rng * * Iterates over the elements rng, passing each in turn to the * block. You can only iterate if the start object of the range * supports the +succ+ method (which means that you can't iterate over * ranges of +Float+ objects). * * (10..15).each do |n| * print n, ' ' * end * * produces: * * 10 11 12 13 14 15 */ mrb_value mrb_range_each(mrb_state *mrb, mrb_value range) { return range; } mrb_int mrb_range_beg_len(mrb_state *mrb, mrb_value range, mrb_int *begp, mrb_int *lenp, mrb_int len, mrb_int err) { mrb_int beg, end, b, e; struct RRange *r = mrb_range_ptr(range); if (mrb_type(range) != MRB_TT_RANGE) return FALSE; beg = b = mrb_fixnum(r->edges->beg); end = e = mrb_fixnum(r->edges->end); if (beg < 0) { beg += len; if (beg < 0) goto out_of_range; } if (err == 0 || err == 2) { if (beg > len) goto out_of_range; if (end > len) end = len; } if (end < 0) end += len; if (!r->excl) end++; /* include end point */ len = end - beg; if (len < 0) len = 0; *begp = beg; *lenp = len; return TRUE; out_of_range: if (err) { mrb_raise(mrb, E_RANGE_ERROR, "%ld..%s%ld out of range", b, r->excl? "." : "", e); } return OTHER; } /* 15.2.14.4.12(x) */ /* * call-seq: * rng.to_s -> string * * Convert this range object to a printable form. */ static mrb_value range_to_s(mrb_state *mrb, mrb_value range) { mrb_value str, str2; struct RRange *r = mrb_range_ptr(range); str = mrb_obj_as_string(mrb, r->edges->beg); str2 = mrb_obj_as_string(mrb, r->edges->end); str = mrb_str_dup(mrb, str); mrb_str_cat(mrb, str, "...", r->excl ? 3 : 2); mrb_str_append(mrb, str, str2); return str; } static mrb_value inspect_range(mrb_state *mrb, mrb_value range, mrb_value dummy, int recur) { mrb_value str, str2; struct RRange *r = mrb_range_ptr(range); if (recur) { static const char s[2][14] = { "(... ... ...)", "(... .. ...)" }; static const int n[] = { 13, 12 }; int idx; idx = (r->excl) ? 0 : 1; return mrb_str_new(mrb, s[idx], n[idx]); } str = mrb_inspect(mrb, r->edges->beg); str2 = mrb_inspect(mrb, r->edges->end); str = mrb_str_dup(mrb, str); mrb_str_cat(mrb, str, "...", r->excl ? 3 : 2); mrb_str_append(mrb, str, str2); return str; } /* 15.2.14.4.13(x) */ /* * call-seq: * rng.inspect -> string * * Convert this range object to a printable form (using * inspect to convert the start and end * objects). */ static mrb_value range_inspect(mrb_state *mrb, mrb_value range) { return inspect_range(mrb, range, range, 0); } /* 15.2.14.4.14(x) */ /* * call-seq: * rng.eql?(obj) -> true or false * * Returns true only if obj is a Range, has equivalent * beginning and end items (by comparing them with #eql?), and has the same * #exclude_end? setting as rng. * * (0..2).eql?(0..2) #=> true * (0..2).eql?(Range.new(0,2)) #=> true * (0..2).eql?(0...2) #=> false * */ static mrb_value range_eql(mrb_state *mrb, mrb_value range) { mrb_value obj; struct RRange *r, *o; mrb_get_args(mrb, "o", &obj); if (mrb_obj_equal(mrb, range, obj)) return mrb_true_value(); if (!mrb_obj_is_kind_of(mrb, obj, RANGE_CLASS)) return mrb_false_value(); r = mrb_range_ptr(range); if (mrb_type(obj) != MRB_TT_RANGE) return mrb_false_value(); o = mrb_range_ptr(obj); if (!mrb_eql(mrb, r->edges->beg, o->edges->beg)) return mrb_false_value(); if (!mrb_eql(mrb, r->edges->end, o->edges->end)) return mrb_false_value(); if (r->excl != o->excl) return mrb_false_value(); return mrb_true_value(); } /* 15.2.14.4.15(x) */ mrb_value range_initialize_copy(mrb_state *mrb, mrb_value copy) { mrb_value src; mrb_get_args(mrb, "o", &src); if (mrb_obj_equal(mrb, copy, src)) return copy; if (!mrb_obj_is_instance_of(mrb, src, mrb_obj_class(mrb, copy))) { mrb_raise(mrb, E_TYPE_ERROR, "wrong argument class"); } memcpy(mrb_range_ptr(copy), mrb_range_ptr(src), sizeof(struct RRange)); return copy; } void mrb_init_range(mrb_state *mrb) { struct RClass *r; r = mrb_define_class(mrb, "Range", mrb->object_class); mrb_include_module(mrb, r, mrb_class_get(mrb, "Enumerable")); mrb_define_method(mrb, r, "begin", mrb_range_beg, ARGS_NONE()); /* 15.2.14.4.3 */ mrb_define_method(mrb, r, "end", mrb_range_end, ARGS_NONE()); /* 15.2.14.4.5 */ mrb_define_method(mrb, r, "==", mrb_range_eq, ARGS_REQ(1)); /* 15.2.14.4.1 */ mrb_define_method(mrb, r, "===", mrb_range_include, ARGS_REQ(1)); /* 15.2.14.4.2 */ mrb_define_method(mrb, r, "each", mrb_range_each, ARGS_NONE()); /* 15.2.14.4.4 */ mrb_define_method(mrb, r, "exclude_end?", mrb_range_excl, ARGS_NONE()); /* 15.2.14.4.6 */ mrb_define_method(mrb, r, "first", mrb_range_beg, ARGS_NONE()); /* 15.2.14.4.7 */ mrb_define_method(mrb, r, "include?", mrb_range_include, ARGS_REQ(1)); /* 15.2.14.4.8 */ mrb_define_method(mrb, r, "initialize", mrb_range_initialize, ARGS_REQ(4)); /* 15.2.14.4.9 */ mrb_define_method(mrb, r, "last", mrb_range_end, ARGS_NONE()); /* 15.2.14.4.10 */ mrb_define_method(mrb, r, "member?", mrb_range_include, ARGS_REQ(1)); /* 15.2.14.4.11 */ mrb_define_method(mrb, r, "to_s", range_to_s, ARGS_NONE()); /* 15.2.14.4.12(x) */ mrb_define_method(mrb, r, "inspect", range_inspect, ARGS_NONE()); /* 15.2.14.4.13(x) */ mrb_define_method(mrb, r, "eql?", range_eql, ARGS_REQ(1)); /* 15.2.14.4.14(x) */ mrb_define_method(mrb, r, "initialize_copy", range_initialize_copy, ARGS_REQ(1)); /* 15.2.14.4.15(x) */ }