#include <mruby.h>
#include <mruby/class.h>
#include <mruby/range.h>
#include <mruby/string.h>
#include <mruby/array.h>
#define RANGE_INITIALIZED_MASK 1
#define RANGE_INITIALIZED(p) ((p)->flags |= RANGE_INITIALIZED_MASK)
#define RANGE_INITIALIZED_P(p) ((p)->flags & RANGE_INITIALIZED_MASK)
static void
r_check(mrb_state *mrb, mrb_value a, mrb_value b)
{
mrb_value ans;
enum mrb_vtype ta;
enum mrb_vtype tb;
ta = mrb_type(a);
tb = mrb_type(b);
#ifdef MRB_WITHOUT_FLOAT
if (ta == MRB_TT_FIXNUM && tb == MRB_TT_FIXNUM ) {
#else
if ((ta == MRB_TT_FIXNUM || ta == MRB_TT_FLOAT) &&
(tb == MRB_TT_FIXNUM || tb == MRB_TT_FLOAT)) {
#endif
return;
}
ans = mrb_funcall(mrb, a, "<=>", 1, b);
if (mrb_nil_p(ans)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "bad value for range");
}
}
static mrb_bool
r_le(mrb_state *mrb, mrb_value a, mrb_value b)
{
mrb_value r = mrb_funcall(mrb, a, "<=>", 1, b);
if (mrb_fixnum_p(r)) {
mrb_int c = mrb_fixnum(r);
if (c == 0 || c == -1) return TRUE;
}
return FALSE;
}
static mrb_bool
r_gt(mrb_state *mrb, mrb_value a, mrb_value b)
{
mrb_value r = mrb_funcall(mrb, a, "<=>", 1, b);
return mrb_fixnum_p(r) && mrb_fixnum(r) == 1;
}
static mrb_bool
r_ge(mrb_state *mrb, mrb_value a, mrb_value b)
{
mrb_value r = mrb_funcall(mrb, a, "<=>", 1, b);
if (mrb_fixnum_p(r)) {
mrb_int c = mrb_fixnum(r);
if (c == 0 || c == 1) return TRUE;
}
return FALSE;
}
static void
range_ptr_alloc_edges(mrb_state *mrb, struct RRange *r)
{
#ifndef MRB_RANGE_EMBED
r->edges = (mrb_range_edges *)mrb_malloc(mrb, sizeof(mrb_range_edges));
#endif
}
static struct RRange *
range_ptr_init(mrb_state *mrb, struct RRange *r, mrb_value beg, mrb_value end, mrb_bool excl)
{
r_check(mrb, beg, end);
if (r) {
if (RANGE_INITIALIZED_P(r)) {
mrb_name_error(mrb, mrb_intern_lit(mrb, "initialize"), "`initialize' called twice");
}
else {
range_ptr_alloc_edges(mrb, r);
}
}
else {
r = (struct RRange*)mrb_obj_alloc(mrb, MRB_TT_RANGE, mrb->range_class);
range_ptr_alloc_edges(mrb, r);
}
RANGE_BEG(r) = beg;
RANGE_END(r) = end;
RANGE_EXCL(r) = excl;
RANGE_INITIALIZED(r);
return r;
}
static void
range_ptr_replace(mrb_state *mrb, struct RRange *r, mrb_value beg, mrb_value end, mrb_bool excl)
{
range_ptr_init(mrb, r, beg, end, excl);
mrb_write_barrier(mrb, (struct RBasic*)r);
}
static mrb_value
range_beg(mrb_state *mrb, mrb_value range)
{
return mrb_range_beg(mrb, range);
}
static mrb_value
range_end(mrb_state *mrb, mrb_value range)
{
return mrb_range_end(mrb, range);
}
static mrb_value
range_excl(mrb_state *mrb, mrb_value range)
{
return mrb_bool_value(mrb_range_excl_p(mrb, range));
}
static mrb_value
range_initialize(mrb_state *mrb, mrb_value range)
{
mrb_value beg, end;
mrb_bool exclusive = FALSE;
mrb_get_args(mrb, "oo|b", &beg, &end, &exclusive);
range_ptr_replace(mrb, mrb_range_raw_ptr(range), beg, end, exclusive);
return range;
}
static mrb_value
range_eq(mrb_state *mrb, mrb_value range)
{
struct RRange *rr;
struct RRange *ro;
mrb_value obj, v1, v2;
mrb_get_args(mrb, "o", &obj);
if (mrb_obj_equal(mrb, range, obj)) return mrb_true_value();
if (!mrb_obj_is_instance_of(mrb, obj, mrb_obj_class(mrb, range))) {
return mrb_false_value();
}
rr = mrb_range_ptr(mrb, range);
ro = mrb_range_ptr(mrb, obj);
v1 = mrb_funcall(mrb, RANGE_BEG(rr), "==", 1, RANGE_BEG(ro));
v2 = mrb_funcall(mrb, RANGE_END(rr), "==", 1, RANGE_END(ro));
if (!mrb_bool(v1) || !mrb_bool(v2) || RANGE_EXCL(rr) != RANGE_EXCL(ro)) {
return mrb_false_value();
}
return mrb_true_value();
}
static mrb_value
range_include(mrb_state *mrb, mrb_value range)
{
mrb_value val;
struct RRange *r = mrb_range_ptr(mrb, range);
mrb_value beg, end;
mrb_bool include_p;
mrb_get_args(mrb, "o", &val);
beg = RANGE_BEG(r);
end = RANGE_END(r);
include_p = r_le(mrb, beg, val) &&
(RANGE_EXCL(r) ? r_gt(mrb, end, val)
: r_ge(mrb, end, val));
return mrb_bool_value(include_p);
}
static mrb_value
range_to_s(mrb_state *mrb, mrb_value range)
{
mrb_value str, str2;
struct RRange *r = mrb_range_ptr(mrb, range);
str = mrb_obj_as_string(mrb, RANGE_BEG(r));
str2 = mrb_obj_as_string(mrb, RANGE_END(r));
str = mrb_str_dup(mrb, str);
mrb_str_cat(mrb, str, "...", RANGE_EXCL(r) ? 3 : 2);
mrb_str_cat_str(mrb, str, str2);
return str;
}
static mrb_value
range_inspect(mrb_state *mrb, mrb_value range)
{
mrb_value str, str2;
struct RRange *r = mrb_range_ptr(mrb, range);
str = mrb_inspect(mrb, RANGE_BEG(r));
str2 = mrb_inspect(mrb, RANGE_END(r));
str = mrb_str_dup(mrb, str);
mrb_str_cat(mrb, str, "...", RANGE_EXCL(r) ? 3 : 2);
mrb_str_cat_str(mrb, str, str2);
return str;
}
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, mrb->range_class)) return mrb_false_value();
if (mrb_type(obj) != MRB_TT_RANGE) return mrb_false_value();
r = mrb_range_ptr(mrb, range);
o = mrb_range_ptr(mrb, obj);
if (!mrb_eql(mrb, RANGE_BEG(r), RANGE_BEG(o)) ||
!mrb_eql(mrb, RANGE_END(r), RANGE_END(o)) ||
(RANGE_EXCL(r) != RANGE_EXCL(o))) {
return mrb_false_value();
}
return mrb_true_value();
}
static mrb_value
range_initialize_copy(mrb_state *mrb, mrb_value copy)
{
mrb_value src;
struct RRange *r;
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");
}
r = mrb_range_ptr(mrb, src);
range_ptr_replace(mrb, mrb_range_raw_ptr(copy), RANGE_BEG(r), RANGE_END(r), RANGE_EXCL(r));
return copy;
}
mrb_value
mrb_get_values_at(mrb_state *mrb, mrb_value obj, mrb_int olen, mrb_int argc, const mrb_value *argv, mrb_value (*func)(mrb_state*, mrb_value, mrb_int))
{
mrb_int i, j, beg, len;
mrb_value result;
result = mrb_ary_new(mrb);
for (i = 0; i < argc; ++i) {
if (mrb_fixnum_p(argv[i])) {
mrb_ary_push(mrb, result, func(mrb, obj, mrb_fixnum(argv[i])));
}
else if (mrb_range_beg_len(mrb, argv[i], &beg, &len, olen, FALSE) == 1) {
mrb_int const end = olen < beg + len ? olen : beg + len;
for (j = beg; j < end; ++j) {
mrb_ary_push(mrb, result, func(mrb, obj, j));
}
for (; j < beg + len; ++j) {
mrb_ary_push(mrb, result, mrb_nil_value());
}
}
else {
mrb_raisef(mrb, E_TYPE_ERROR, "invalid values selector: %S", argv[i]);
}
}
return result;
}
void
mrb_gc_mark_range(mrb_state *mrb, struct RRange *r)
{
if (RANGE_INITIALIZED_P(r)) {
mrb_gc_mark_value(mrb, RANGE_BEG(r));
mrb_gc_mark_value(mrb, RANGE_END(r));
}
}
MRB_API struct RRange*
mrb_range_ptr(mrb_state *mrb, mrb_value range)
{
struct RRange *r = mrb_range_raw_ptr(range);
if (!RANGE_INITIALIZED_P(r)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized range");
}
return r;
}
MRB_API mrb_value
mrb_range_new(mrb_state *mrb, mrb_value beg, mrb_value end, mrb_bool excl)
{
struct RRange *r = range_ptr_init(mrb, NULL, beg, end, excl);
return mrb_range_value(r);
}
MRB_API mrb_int
mrb_range_beg_len(mrb_state *mrb, mrb_value range, mrb_int *begp, mrb_int *lenp, mrb_int len, mrb_bool trunc)
{
mrb_int beg, end;
struct RRange *r;
if (mrb_type(range) != MRB_TT_RANGE) return 0;
r = mrb_range_ptr(mrb, range);
beg = mrb_int(mrb, RANGE_BEG(r));
end = mrb_int(mrb, RANGE_END(r));
if (beg < 0) {
beg += len;
if (beg < 0) return 2;
}
if (trunc) {
if (beg > len) return 2;
if (end > len) end = len;
}
if (end < 0) end += len;
if (!RANGE_EXCL(r) && (!trunc || end < len)) end++;
len = end - beg;
if (len < 0) len = 0;
*begp = beg;
*lenp = len;
return 1;
}
void
mrb_init_range(mrb_state *mrb)
{
struct RClass *r;
r = mrb_define_class(mrb, "Range", mrb->object_class);
mrb->range_class = r;
MRB_SET_INSTANCE_TT(r, MRB_TT_RANGE);
mrb_define_method(mrb, r, "begin", range_beg, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "end", range_end, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "==", range_eq, MRB_ARGS_REQ(1));
mrb_define_method(mrb, r, "===", range_include, MRB_ARGS_REQ(1));
mrb_define_method(mrb, r, "exclude_end?", range_excl, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "first", range_beg, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "include?", range_include, MRB_ARGS_REQ(1));
mrb_define_method(mrb, r, "initialize", range_initialize, MRB_ARGS_ANY());
mrb_define_method(mrb, r, "last", range_end, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "member?", range_include, MRB_ARGS_REQ(1));
mrb_define_method(mrb, r, "to_s", range_to_s, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "inspect", range_inspect, MRB_ARGS_NONE());
mrb_define_method(mrb, r, "eql?", range_eql, MRB_ARGS_REQ(1));
mrb_define_method(mrb, r, "initialize_copy", range_initialize_copy, MRB_ARGS_REQ(1));
}