struct.c 19.9 KB
Newer Older
mimaki's avatar
mimaki committed
1 2
/*
** struct.c - Struct class
roco's avatar
roco committed
3
**
mimaki's avatar
mimaki committed
4 5
** See Copyright Notice in mruby.h
*/
mimaki's avatar
mimaki committed
6 7

#include <string.h>
8 9 10 11 12 13 14
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/variable.h>
#include <mruby/hash.h>
#include <mruby/range.h>
mimaki's avatar
mimaki committed
15

16 17
#define RSTRUCT_LEN(st) mrb_ary_ptr(st)->len
#define RSTRUCT_PTR(st) mrb_ary_ptr(st)->ptr
mimaki's avatar
mimaki committed
18 19 20 21 22 23 24 25 26 27 28 29 30

static struct RClass *
struct_class(mrb_state *mrb)
{
  return mrb_class_get(mrb, "Struct");
}

static inline mrb_value
struct_ivar_get(mrb_state *mrb, mrb_value c, mrb_sym id)
{
  struct RClass* kclass;
  struct RClass* sclass = struct_class(mrb);
  mrb_value ans;
31 32 33 34 35 36 37 38 39

  for (;;) {
    ans = mrb_iv_get(mrb, c, id);
    if (!mrb_nil_p(ans)) return ans;
    kclass = RCLASS_SUPER(c);
    if (kclass == 0 || kclass == sclass)
      return mrb_nil_value();
    c = mrb_obj_value(kclass);
  }
mimaki's avatar
mimaki committed
40 41
}

42
static mrb_value
43
struct_s_members(mrb_state *mrb, struct RClass *klass)
mimaki's avatar
mimaki committed
44
{
45
  mrb_value members = struct_ivar_get(mrb, mrb_obj_value(klass), mrb_intern_lit(mrb, "__members__"));
mimaki's avatar
mimaki committed
46

47 48 49 50 51 52 53
  if (mrb_nil_p(members)) {
    mrb_raise(mrb, E_TYPE_ERROR, "uninitialized struct");
  }
  if (!mrb_array_p(members)) {
    mrb_raise(mrb, E_TYPE_ERROR, "corrupted struct");
  }
  return members;
mimaki's avatar
mimaki committed
54 55
}

56
static mrb_value
57 58 59 60 61 62 63
struct_members(mrb_state *mrb, mrb_value s)
{
  mrb_value members = struct_s_members(mrb, mrb_obj_class(mrb, s));
  if (!mrb_array_p(s)) {
    mrb_raise(mrb, E_TYPE_ERROR, "corrupted struct");
  }
  if (RSTRUCT_LEN(s) != RARRAY_LEN(members)) {
64 65 66 67 68 69 70 71
    if (RSTRUCT_LEN(s) == 0) {  /* probably uninitialized */
      mrb_ary_resize(mrb, s, RARRAY_LEN(members));
    }
    else {
      mrb_raisef(mrb, E_TYPE_ERROR,
                 "struct size differs (%S required %S given)",
                 mrb_fixnum_value(RARRAY_LEN(members)), mrb_fixnum_value(RSTRUCT_LEN(s)));
    }
mimaki's avatar
mimaki committed
72 73 74 75 76 77 78
  }
  return members;
}

static mrb_value
mrb_struct_s_members_m(mrb_state *mrb, mrb_value klass)
{
79 80
  mrb_value members, ary;

81
  members = struct_s_members(mrb, mrb_class_ptr(klass));
82
  ary = mrb_ary_new_capa(mrb, RARRAY_LEN(members));
83
  mrb_ary_replace(mrb, ary, members);
84
  return ary;
mimaki's avatar
mimaki committed
85 86
}

take_cheeze's avatar
take_cheeze committed
87 88 89 90 91 92 93 94 95 96
static void
mrb_struct_modify(mrb_state *mrb, mrb_value strct)
{
  if (MRB_FROZEN_P(mrb_basic_ptr(strct))) {
    mrb_raise(mrb, E_RUNTIME_ERROR, "can't modify frozen struct");
  }

  mrb_write_barrier(mrb, mrb_basic_ptr(strct));
}

mimaki's avatar
mimaki committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110
/* 15.2.18.4.6  */
/*
 *  call-seq:
 *     struct.members    -> array
 *
 *  Returns an array of strings representing the names of the instance
 *  variables.
 *
 *     Customer = Struct.new(:name, :address, :zip)
 *     joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
 *     joe.members   #=> [:name, :address, :zip]
 */

static mrb_value
111
mrb_struct_members(mrb_state *mrb, mrb_value obj)
mimaki's avatar
mimaki committed
112
{
113
  return mrb_struct_s_members_m(mrb, mrb_obj_value(mrb_obj_class(mrb, obj)));
mimaki's avatar
mimaki committed
114 115
}

116
static mrb_value struct_aref_sym(mrb_state *mrb, mrb_value obj, mrb_sym id);
mimaki's avatar
mimaki committed
117 118 119 120

static mrb_value
mrb_struct_ref(mrb_state *mrb, mrb_value obj)
{
121
  return struct_aref_sym(mrb, obj, mrb->c->ci->mid);
mimaki's avatar
mimaki committed
122 123
}

124
static mrb_sym
125
mrb_id_attrset(mrb_state *mrb, mrb_sym id)
mimaki's avatar
mimaki committed
126
{
127 128
  const char *name;
  char *buf;
129
  mrb_int len;
130 131 132
  mrb_sym mid;

  name = mrb_sym2name_len(mrb, id, &len);
133 134
  buf = (char *)mrb_malloc(mrb, (size_t)len+2);
  memcpy(buf, name, (size_t)len);
135 136 137
  buf[len] = '=';
  buf[len+1] = '\0';

138
  mid = mrb_intern(mrb, buf, len+1);
139 140
  mrb_free(mrb, buf);
  return mid;
mimaki's avatar
mimaki committed
141 142
}

143 144
static mrb_value mrb_struct_aset_sym(mrb_state *mrb, mrb_value s, mrb_sym id, mrb_value val);

mimaki's avatar
mimaki committed
145
static mrb_value
146
mrb_struct_set_m(mrb_state *mrb, mrb_value obj)
mimaki's avatar
mimaki committed
147
{
148 149
  mrb_value val;

150
  const char *name;
151
  mrb_int slen;
152
  mrb_sym mid;
153 154

  mrb_get_args(mrb, "o", &val);
155 156

  /* get base id */
157 158
  name = mrb_sym2name_len(mrb, mrb->c->ci->mid, &slen);
  mid = mrb_intern(mrb, name, slen-1); /* omit last "=" */
159

160
  return mrb_struct_aset_sym(mrb, obj, mid, val);
161 162
}

163
static mrb_bool
164
is_local_id(mrb_state *mrb, const char *name)
mimaki's avatar
mimaki committed
165
{
166
  if (!name) return FALSE;
167
  return !ISUPPER(name[0]);
mimaki's avatar
mimaki committed
168 169
}

170
static mrb_bool
171
is_const_id(mrb_state *mrb, const char *name)
mimaki's avatar
mimaki committed
172
{
173
  if (!name) return FALSE;
174
  return ISUPPER(name[0]);
mimaki's avatar
mimaki committed
175 176
}

177 178 179
static void
make_struct_define_accessors(mrb_state *mrb, mrb_value members, struct RClass *c)
{
180
  const mrb_value *ptr_members = RARRAY_PTR(members);
181 182 183 184 185 186 187 188 189
  mrb_int i;
  mrb_int len = RARRAY_LEN(members);
  int ai = mrb_gc_arena_save(mrb);

  for (i=0; i<len; i++) {
    mrb_sym id = mrb_symbol(ptr_members[i]);
    const char *name = mrb_sym2name_len(mrb, id, NULL);

    if (is_local_id(mrb, name) || is_const_id(mrb, name)) {
190
      mrb_define_method_id(mrb, c, id, mrb_struct_ref, MRB_ARGS_NONE());
191 192 193 194 195 196
      mrb_define_method_id(mrb, c, mrb_id_attrset(mrb, id), mrb_struct_set_m, MRB_ARGS_REQ(1));
      mrb_gc_arena_restore(mrb, ai);
    }
  }
}

mimaki's avatar
mimaki committed
197 198 199
static mrb_value
make_struct(mrb_state *mrb, mrb_value name, mrb_value members, struct RClass * klass)
{
200
  mrb_value nstr;
201 202
  mrb_sym id;
  struct RClass *c;
mimaki's avatar
mimaki committed
203

204 205 206 207 208 209
  if (mrb_nil_p(name)) {
    c = mrb_class_new(mrb, klass);
  }
  else {
    /* old style: should we warn? */
    name = mrb_str_to_str(mrb, name);
210
    id = mrb_obj_to_sym(mrb, name);
211
    if (!is_const_id(mrb, mrb_sym2name_len(mrb, id, NULL))) {
212
      mrb_name_error(mrb, id, "identifier %S needs to be constant", name);
mimaki's avatar
mimaki committed
213
    }
214
    if (mrb_const_defined_at(mrb, mrb_obj_value(klass), id)) {
215
      mrb_warn(mrb, "redefining constant Struct::%S", name);
216
      mrb_const_remove(mrb, mrb_obj_value(klass), id);
mimaki's avatar
mimaki committed
217
    }
218 219
    c = mrb_define_class_under(mrb, klass, RSTRING_PTR(name), klass);
  }
220
  MRB_SET_INSTANCE_TT(c, MRB_TT_ARRAY);
221
  nstr = mrb_obj_value(c);
222
  mrb_iv_set(mrb, nstr, mrb_intern_lit(mrb, "__members__"), members);
223

224 225 226
  mrb_define_class_method(mrb, c, "new", mrb_instance_new, MRB_ARGS_ANY());
  mrb_define_class_method(mrb, c, "[]", mrb_instance_new, MRB_ARGS_ANY());
  mrb_define_class_method(mrb, c, "members", mrb_struct_s_members_m, MRB_ARGS_NONE());
227
  /* RSTRUCT(nstr)->basic.c->super = c->c; */
228
  make_struct_define_accessors(mrb, members, c);
229
  return nstr;
mimaki's avatar
mimaki committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
}

/* 15.2.18.3.1  */
/*
 *  call-seq:
 *     Struct.new( [aString] [, aSym]+> )    -> StructClass
 *     StructClass.new(arg, ...)             -> obj
 *     StructClass[arg, ...]                 -> obj
 *
 *  Creates a new class, named by <i>aString</i>, containing accessor
 *  methods for the given symbols. If the name <i>aString</i> is
 *  omitted, an anonymous structure class will be created. Otherwise,
 *  the name of this struct will appear as a constant in class
 *  <code>Struct</code>, so it must be unique for all
 *  <code>Struct</code>s in the system and should start with a capital
 *  letter. Assigning a structure class to a constant effectively gives
 *  the class the name of the constant.
 *
 *  <code>Struct::new</code> returns a new <code>Class</code> object,
 *  which can then be used to create specific instances of the new
 *  structure. The number of actual parameters must be
 *  less than or equal to the number of attributes defined for this
 *  class; unset parameters default to <code>nil</code>.  Passing too many
 *  parameters will raise an <code>ArgumentError</code>.
 *
 *  The remaining methods listed in this section (class and instance)
 *  are defined for this generated class.
 *
 *     # Create a structure with a name in Struct
 *     Struct.new("Customer", :name, :address)    #=> Struct::Customer
 *     Struct::Customer.new("Dave", "123 Main")   #=> #<struct Struct::Customer name="Dave", address="123 Main">
 *
 *     # Create a structure named by its constant
 *     Customer = Struct.new(:name, :address)     #=> Customer
 *     Customer.new("Dave", "123 Main")           #=> #<struct Customer name="Dave", address="123 Main">
 */
static mrb_value
mrb_struct_s_def(mrb_state *mrb, mrb_value klass)
{
  mrb_value name, rest;
  mrb_value *pargv;
271
  mrb_int argcnt;
272
  mrb_int i;
mimaki's avatar
mimaki committed
273 274 275
  mrb_value b, st;
  mrb_sym id;
  mrb_value *argv;
276
  mrb_int argc;
mimaki's avatar
mimaki committed
277 278 279

  name = mrb_nil_value();
  rest = mrb_nil_value();
Yukihiro Matsumoto's avatar
Yukihiro Matsumoto committed
280
  mrb_get_args(mrb, "*&", &argv, &argc, &b);
281 282
  if (argc == 0) { /* special case to avoid crash */
    rest = mrb_ary_new(mrb);
283 284
  }
  else {
285
    if (argc > 0) name = argv[0];
286 287 288 289 290 291 292
    pargv = &argv[1];
    argcnt = argc-1;
    if (!mrb_nil_p(name) && mrb_symbol_p(name)) {
      /* 1stArgument:symbol -> name=nil rest=argv[0]-[n] */
      name = mrb_nil_value();
      pargv = &argv[0];
      argcnt++;
mimaki's avatar
mimaki committed
293
    }
294
    rest = mrb_ary_new_from_values(mrb, argcnt, pargv);
295
    for (i=0; i<RARRAY_LEN(rest); i++) {
296
      id = mrb_obj_to_sym(mrb, RARRAY_PTR(rest)[i]);
297
      mrb_ary_set(mrb, rest, i, mrb_symbol_value(id));
298
    }
299
  }
300
  st = make_struct(mrb, name, rest, mrb_class_ptr(klass));
mimaki's avatar
mimaki committed
301
  if (!mrb_nil_p(b)) {
ksss's avatar
ksss committed
302
    mrb_yield_with_class(mrb, b, 1, &st, st, mrb_class_ptr(st));
mimaki's avatar
mimaki committed
303 304 305 306 307
  }

  return st;
}

308
static mrb_int
mimaki's avatar
mimaki committed
309 310
num_members(mrb_state *mrb, struct RClass *klass)
{
311 312
  mrb_value members;

313
  members = struct_ivar_get(mrb, mrb_obj_value(klass), mrb_intern_lit(mrb, "__members__"));
314 315 316 317
  if (!mrb_array_p(members)) {
    mrb_raise(mrb, E_TYPE_ERROR, "broken members");
  }
  return RARRAY_LEN(members);
mimaki's avatar
mimaki committed
318 319 320 321 322 323
}

/* 15.2.18.4.8  */
/*
 */
static mrb_value
324
mrb_struct_initialize_withArg(mrb_state *mrb, mrb_int argc, mrb_value *argv, mrb_value self)
mimaki's avatar
mimaki committed
325 326
{
  struct RClass *klass = mrb_obj_class(mrb, self);
327
  mrb_int i, n;
mimaki's avatar
mimaki committed
328 329 330 331 332

  n = num_members(mrb, klass);
  if (n < argc) {
    mrb_raise(mrb, E_ARGUMENT_ERROR, "struct size differs");
  }
333

334
  for (i = 0; i < argc; i++) {
335
    mrb_ary_set(mrb, self, i, argv[i]);
336
  }
mattn's avatar
mattn committed
337
  for (i = argc; i < n; i++) {
338
    mrb_ary_set(mrb, self, i, mrb_nil_value());
mattn's avatar
mattn committed
339
  }
mimaki's avatar
mimaki committed
340 341 342 343
  return self;
}

static mrb_value
344
mrb_struct_initialize(mrb_state *mrb, mrb_value self)
mimaki's avatar
mimaki committed
345 346
{
  mrb_value *argv;
347
  mrb_int argc;
mimaki's avatar
mimaki committed
348 349 350 351 352 353 354

  mrb_get_args(mrb, "*", &argv, &argc);
  return mrb_struct_initialize_withArg(mrb, argc, argv, self);
}

/* 15.2.18.4.9  */
/* :nodoc: */
355
static mrb_value
mimaki's avatar
mimaki committed
356 357
mrb_struct_init_copy(mrb_state *mrb, mrb_value copy)
{
358
  mrb_value s;
359

mimaki's avatar
mimaki committed
360 361
  mrb_get_args(mrb, "o", &s);

362
  if (mrb_obj_equal(mrb, copy, s)) return copy;
363 364 365 366 367 368
  if (!mrb_obj_is_instance_of(mrb, s, mrb_obj_class(mrb, copy))) {
    mrb_raise(mrb, E_TYPE_ERROR, "wrong argument class");
  }
  if (!mrb_array_p(s)) {
    mrb_raise(mrb, E_TYPE_ERROR, "corrupted struct");
  }
369
  mrb_ary_replace(mrb, copy, s);
370
  return copy;
mimaki's avatar
mimaki committed
371 372 373
}

static mrb_value
374
struct_aref_sym(mrb_state *mrb, mrb_value obj, mrb_sym id)
mimaki's avatar
mimaki committed
375
{
376
  mrb_value members, *ptr;
377
  const mrb_value *ptr_members;
378
  mrb_int i, len;
379

380
  members = struct_members(mrb, obj);
381 382
  ptr_members = RARRAY_PTR(members);
  len = RARRAY_LEN(members);
383
  ptr = RSTRUCT_PTR(obj);
384
  for (i=0; i<len; i++) {
385 386
    mrb_value slot = ptr_members[i];
    if (mrb_symbol_p(slot) && mrb_symbol(slot) == id) {
387
      return ptr[i];
mimaki's avatar
mimaki committed
388
    }
389
  }
390
  mrb_raisef(mrb, E_INDEX_ERROR, "'%S' is not a struct member", mrb_sym2str(mrb, id));
391
  return mrb_nil_value();       /* not reached */
mimaki's avatar
mimaki committed
392 393
}

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
static mrb_value
struct_aref_int(mrb_state *mrb, mrb_value s, mrb_int i)
{
  if (i < 0) i = RSTRUCT_LEN(s) + i;
  if (i < 0)
      mrb_raisef(mrb, E_INDEX_ERROR,
                 "offset %S too small for struct(size:%S)",
                 mrb_fixnum_value(i), mrb_fixnum_value(RSTRUCT_LEN(s)));
  if (RSTRUCT_LEN(s) <= i)
    mrb_raisef(mrb, E_INDEX_ERROR,
               "offset %S too large for struct(size:%S)",
               mrb_fixnum_value(i), mrb_fixnum_value(RSTRUCT_LEN(s)));
  return RSTRUCT_PTR(s)[i];
}

mimaki's avatar
mimaki committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
/* 15.2.18.4.2  */
/*
 *  call-seq:
 *     struct[symbol]    -> anObject
 *     struct[fixnum]    -> anObject
 *
 *  Attribute Reference---Returns the value of the instance variable
 *  named by <i>symbol</i>, or indexed (0..length-1) by
 *  <i>fixnum</i>. Will raise <code>NameError</code> if the named
 *  variable does not exist, or <code>IndexError</code> if the index is
 *  out of range.
 *
 *     Customer = Struct.new(:name, :address, :zip)
 *     joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
 *
 *     joe["name"]   #=> "Joe Smith"
 *     joe[:name]    #=> "Joe Smith"
 *     joe[0]        #=> "Joe Smith"
 */
428
static mrb_value
429
mrb_struct_aref(mrb_state *mrb, mrb_value s)
mimaki's avatar
mimaki committed
430
{
431
  mrb_value idx;
mimaki's avatar
mimaki committed
432

433
  mrb_get_args(mrb, "o", &idx);
434 435 436 437
  if (mrb_string_p(idx)) {
    mrb_value sym = mrb_check_intern_str(mrb, idx);

    if (mrb_nil_p(sym)) {
438
      mrb_name_error(mrb, mrb_intern_str(mrb, idx), "no member '%S' in struct", idx);
439 440 441 442
    }
    idx = sym;
  }
  if (mrb_symbol_p(idx)) {
443
    return struct_aref_sym(mrb, s, mrb_symbol(idx));
mimaki's avatar
mimaki committed
444
  }
445
  return struct_aref_int(mrb, s, mrb_int(mrb, idx));
mimaki's avatar
mimaki committed
446 447 448
}

static mrb_value
449
mrb_struct_aset_sym(mrb_state *mrb, mrb_value s, mrb_sym id, mrb_value val)
mimaki's avatar
mimaki committed
450
{
451 452
  mrb_value members, *ptr;
  const mrb_value *ptr_members;
453
  mrb_int i, len;
mimaki's avatar
mimaki committed
454

455
  members = struct_members(mrb, s);
456 457 458 459 460
  len = RARRAY_LEN(members);
  ptr = RSTRUCT_PTR(s);
  ptr_members = RARRAY_PTR(members);
  for (i=0; i<len; i++) {
    if (mrb_symbol(ptr_members[i]) == id) {
take_cheeze's avatar
take_cheeze committed
461
      mrb_struct_modify(mrb, s);
462 463
      ptr[i] = val;
      return val;
mimaki's avatar
mimaki committed
464
    }
465
  }
466
  mrb_name_error(mrb, id, "no member '%S' in struct", mrb_sym2str(mrb, id));
467
  return val;                   /* not reach */
mimaki's avatar
mimaki committed
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
}

/* 15.2.18.4.3  */
/*
 *  call-seq:
 *     struct[symbol] = obj    -> obj
 *     struct[fixnum] = obj    -> obj
 *
 *  Attribute Assignment---Assigns to the instance variable named by
 *  <i>symbol</i> or <i>fixnum</i> the value <i>obj</i> and
 *  returns it. Will raise a <code>NameError</code> if the named
 *  variable does not exist, or an <code>IndexError</code> if the index
 *  is out of range.
 *
 *     Customer = Struct.new(:name, :address, :zip)
 *     joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
 *
 *     joe["name"] = "Luke"
 *     joe[:zip]   = "90210"
 *
 *     joe.name   #=> "Luke"
 *     joe.zip    #=> "90210"
 */

492
static mrb_value
mimaki's avatar
mimaki committed
493 494
mrb_struct_aset(mrb_state *mrb, mrb_value s)
{
495
  mrb_int i;
mimaki's avatar
mimaki committed
496 497
  mrb_value idx;
  mrb_value val;
498

mimaki's avatar
mimaki committed
499 500
  mrb_get_args(mrb, "oo", &idx, &val);

501 502 503 504
  if (mrb_string_p(idx)) {
    mrb_value sym = mrb_check_intern_str(mrb, idx);

    if (mrb_nil_p(sym)) {
505
      mrb_name_error(mrb, mrb_intern_str(mrb, idx), "no member '%S' in struct", idx);
506 507 508
    }
    idx = sym;
  }
509 510
  if (mrb_symbol_p(idx)) {
    return mrb_struct_aset_sym(mrb, s, mrb_symbol(idx), val);
511
  }
mimaki's avatar
mimaki committed
512

513
  i = mrb_int(mrb, idx);
514 515
  if (i < 0) i = RSTRUCT_LEN(s) + i;
  if (i < 0) {
516
    mrb_raisef(mrb, E_INDEX_ERROR,
517 518
               "offset %S too small for struct(size:%S)",
               mrb_fixnum_value(i), mrb_fixnum_value(RSTRUCT_LEN(s)));
519 520
  }
  if (RSTRUCT_LEN(s) <= i) {
521
    mrb_raisef(mrb, E_INDEX_ERROR,
522 523
               "offset %S too large for struct(size:%S)",
               mrb_fixnum_value(i), mrb_fixnum_value(RSTRUCT_LEN(s)));
524
  }
take_cheeze's avatar
take_cheeze committed
525
  mrb_struct_modify(mrb, s);
526
  return RSTRUCT_PTR(s)[i] = val;
mimaki's avatar
mimaki committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
}

/* 15.2.18.4.1  */
/*
 *  call-seq:
 *     struct == other_struct     -> true or false
 *
 *  Equality---Returns <code>true</code> if <i>other_struct</i> is
 *  equal to this one: they must be of the same class as generated by
 *  <code>Struct::new</code>, and the values of all instance variables
 *  must be equal (according to <code>Object#==</code>).
 *
 *     Customer = Struct.new(:name, :address, :zip)
 *     joe   = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
 *     joejr = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345)
 *     jane  = Customer.new("Jane Doe", "456 Elm, Anytown NC", 12345)
 *     joe == joejr   #=> true
 *     joe == jane    #=> false
 */

static mrb_value
mrb_struct_equal(mrb_state *mrb, mrb_value s)
{
  mrb_value s2;
551
  mrb_value *ptr, *ptr2;
552
  mrb_int i, len;
mimaki's avatar
mimaki committed
553 554

  mrb_get_args(mrb, "o", &s2);
555
  if (mrb_obj_equal(mrb, s, s2)) {
556
    return mrb_true_value();
557
  }
558
  if (mrb_obj_class(mrb, s) != mrb_obj_class(mrb, s2)) {
559
    return mrb_false_value();
560
  }
561
  if (RSTRUCT_LEN(s) != RSTRUCT_LEN(s2)) {
562
    mrb_bug(mrb, "inconsistent struct"); /* should never happen */
mimaki's avatar
mimaki committed
563
  }
564 565 566 567 568 569
  ptr = RSTRUCT_PTR(s);
  ptr2 = RSTRUCT_PTR(s2);
  len = RSTRUCT_LEN(s);
  for (i=0; i<len; i++) {
    if (!mrb_equal(mrb, ptr[i], ptr2[i])) {
      return mrb_false_value();
570
    }
571
  }
572

573
  return mrb_true_value();
mimaki's avatar
mimaki committed
574 575 576 577 578 579 580 581 582 583 584 585 586 587
}

/* 15.2.18.4.12(x)  */
/*
 * code-seq:
 *   struct.eql?(other)   -> true or false
 *
 * Two structures are equal if they are the same object, or if all their
 * fields are equal (using <code>eql?</code>).
 */
static mrb_value
mrb_struct_eql(mrb_state *mrb, mrb_value s)
{
  mrb_value s2;
588
  mrb_value *ptr, *ptr2;
589
  mrb_int i, len;
mimaki's avatar
mimaki committed
590 591

  mrb_get_args(mrb, "o", &s2);
592
  if (mrb_obj_equal(mrb, s, s2)) {
593
    return mrb_true_value();
594
  }
595
  if (mrb_obj_class(mrb, s) != mrb_obj_class(mrb, s2)) {
596
    return mrb_false_value();
597
  }
598
  if (RSTRUCT_LEN(s) != RSTRUCT_LEN(s2)) {
599
    mrb_bug(mrb, "inconsistent struct"); /* should never happen */
mimaki's avatar
mimaki committed
600
  }
601 602 603 604 605 606
  ptr = RSTRUCT_PTR(s);
  ptr2 = RSTRUCT_PTR(s2);
  len = RSTRUCT_LEN(s);
  for (i=0; i<len; i++) {
    if (!mrb_eql(mrb, ptr[i], ptr2[i])) {
      return mrb_false_value();
607
    }
608
  }
609

610
  return mrb_true_value();
mimaki's avatar
mimaki committed
611 612
}

613 614 615 616 617 618 619 620 621 622 623 624 625
/*
 * call-seq:
 *    struct.length   -> Fixnum
 *    struct.size     -> Fixnum
 *
 * Returns number of struct members.
 */
static mrb_value
mrb_struct_len(mrb_state *mrb, mrb_value self)
{
  return mrb_fixnum_value(RSTRUCT_LEN(self));
}

626 627 628 629 630 631 632 633 634 635 636 637 638
/*
 * call-seq:
 *    struct.to_a    -> array
 *    struct.values  -> array
 *
 * Create an array from struct values.
 */
static mrb_value
mrb_struct_to_a(mrb_state *mrb, mrb_value self)
{
  return mrb_ary_new_from_values(mrb, RSTRUCT_LEN(self), RSTRUCT_PTR(self));
}

take_cheeze's avatar
take_cheeze committed
639 640 641 642 643 644 645 646 647 648 649 650
/*
 * call-seq:
 *    struct.to_h -> hash
 *
 * Create a hash from member names and struct values.
 */
static mrb_value
mrb_struct_to_h(mrb_state *mrb, mrb_value self)
{
  mrb_value members, ret;
  mrb_int i;

651
  members = struct_members(mrb, self);
take_cheeze's avatar
take_cheeze committed
652 653 654 655 656 657 658 659 660
  ret = mrb_hash_new_capa(mrb, RARRAY_LEN(members));

  for (i = 0; i < RARRAY_LEN(members); ++i) {
    mrb_hash_set(mrb, ret, RARRAY_PTR(members)[i], RSTRUCT_PTR(self)[i]);
  }

  return ret;
}

661 662 663 664 665 666 667 668
static mrb_value
mrb_struct_values_at(mrb_state *mrb, mrb_value self)
{
  mrb_int argc;
  mrb_value *argv;

  mrb_get_args(mrb, "*", &argv, &argc);

669
  return mrb_get_values_at(mrb, self, RSTRUCT_LEN(self), argc, argv, struct_aref_int);
670 671
}

mimaki's avatar
mimaki committed
672 673 674 675 676 677 678 679
/*
 *  A <code>Struct</code> is a convenient way to bundle a number of
 *  attributes together, using accessor methods, without having to write
 *  an explicit class.
 *
 *  The <code>Struct</code> class is a generator of specific classes,
 *  each one of which is defined to hold a set of variables and their
 *  accessors. In these examples, we'll call the generated class
680 681
 *  "<i>Customer</i>Class," and we'll show an example instance of that
 *  class as "<i>Customer</i>Inst."
mimaki's avatar
mimaki committed
682 683 684 685 686 687
 *
 *  In the descriptions that follow, the parameter <i>symbol</i> refers
 *  to a symbol, which is either a quoted string or a
 *  <code>Symbol</code> (such as <code>:name</code>).
 */
void
mattn's avatar
mattn committed
688
mrb_mruby_struct_gem_init(mrb_state* mrb)
mimaki's avatar
mimaki committed
689 690 691
{
  struct RClass *st;
  st = mrb_define_class(mrb, "Struct",  mrb->object_class);
692
  MRB_SET_INSTANCE_TT(st, MRB_TT_ARRAY);
mimaki's avatar
mimaki committed
693

694
  mrb_define_class_method(mrb, st, "new",             mrb_struct_s_def,       MRB_ARGS_ANY());  /* 15.2.18.3.1  */
mimaki's avatar
mimaki committed
695

696 697 698
  mrb_define_method(mrb, st,       "==",              mrb_struct_equal,       MRB_ARGS_REQ(1)); /* 15.2.18.4.1  */
  mrb_define_method(mrb, st,       "[]",              mrb_struct_aref,        MRB_ARGS_REQ(1)); /* 15.2.18.4.2  */
  mrb_define_method(mrb, st,       "[]=",             mrb_struct_aset,        MRB_ARGS_REQ(2)); /* 15.2.18.4.3  */
699 700
  mrb_define_method(mrb, st,       "members",         mrb_struct_members,     MRB_ARGS_NONE()); /* 15.2.18.4.6  */
  mrb_define_method(mrb, st,       "initialize",      mrb_struct_initialize,  MRB_ARGS_ANY());  /* 15.2.18.4.8  */
701 702
  mrb_define_method(mrb, st,       "initialize_copy", mrb_struct_init_copy,   MRB_ARGS_REQ(1)); /* 15.2.18.4.9  */
  mrb_define_method(mrb, st,       "eql?",            mrb_struct_eql,         MRB_ARGS_REQ(1)); /* 15.2.18.4.12(x)  */
703 704 705

  mrb_define_method(mrb, st,        "size",           mrb_struct_len,         MRB_ARGS_NONE());
  mrb_define_method(mrb, st,        "length",         mrb_struct_len,         MRB_ARGS_NONE());
706 707
  mrb_define_method(mrb, st,        "to_a",           mrb_struct_to_a,        MRB_ARGS_NONE());
  mrb_define_method(mrb, st,        "values",         mrb_struct_to_a,        MRB_ARGS_NONE());
take_cheeze's avatar
take_cheeze committed
708
  mrb_define_method(mrb, st,        "to_h",           mrb_struct_to_h,        MRB_ARGS_NONE());
709
  mrb_define_method(mrb, st,        "values_at",      mrb_struct_values_at,   MRB_ARGS_ANY());
mimaki's avatar
mimaki committed
710
}
mattn's avatar
mattn committed
711 712 713 714 715

void
mrb_mruby_struct_gem_final(mrb_state* mrb)
{
}