/* ** struct.c - Struct class ** ** See Copyright Notice in mruby.h */ #include #include #include #include #include #include #include #include #include #include #define RSTRUCT_LEN(st) RARRAY_LEN(st) #define RSTRUCT_PTR(st) RARRAY_PTR(st) #define mrb_struct_p(o) (mrb_type(o) == MRB_TT_STRUCT) static struct RClass * struct_class(mrb_state *mrb) { return mrb_class_get_id(mrb, MRB_SYM(Struct)); } static void struct_corrupted(mrb_state *mrb) { mrb_raise(mrb, E_TYPE_ERROR, "corrupted struct"); } static mrb_value struct_s_members(mrb_state *mrb, struct RClass *c) { struct RClass* sclass = struct_class(mrb); for (;;) { mrb_value members = mrb_iv_get(mrb, mrb_obj_value(c), MRB_SYM(__members__)); if (!mrb_nil_p(members)) { if (!mrb_array_p(members)) { struct_corrupted(mrb); } return members; } c = c->super; if (c == sclass || c == 0) { mrb_raise(mrb, E_TYPE_ERROR, "uninitialized struct"); } } } static mrb_value struct_members(mrb_state *mrb, mrb_value s) { if (!mrb_struct_p(s)) { struct_corrupted(mrb); } mrb_value members = struct_s_members(mrb, mrb_obj_class(mrb, s)); mrb_int len = RSTRUCT_LEN(s); mrb_int mlen = RARRAY_LEN(members); if (len > 0 && len != mlen) { mrb_raisef(mrb, E_TYPE_ERROR, "struct size differs (%i required %i given)", mlen, len); } return members; } static mrb_value mrb_struct_s_members_m(mrb_state *mrb, mrb_value klass) { mrb_value members = struct_s_members(mrb, mrb_class_ptr(klass)); mrb_value ary = mrb_ary_new_capa(mrb, RARRAY_LEN(members)); mrb_ary_replace(mrb, ary, members); return ary; } #define mrb_struct_modify(mrb,s) mrb_check_frozen((mrb), mrb_basic_ptr(s)) /* 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 mrb_struct_members(mrb_state *mrb, mrb_value obj) { return mrb_struct_s_members_m(mrb, mrb_obj_value(mrb_obj_class(mrb, obj))); } static mrb_int num_members(mrb_state *mrb, mrb_value self) { mrb_value members = struct_members(mrb, self); return RARRAY_LEN(members); } static mrb_value mrb_struct_ref(mrb_state *mrb, mrb_value obj) { mrb_int argc = mrb_get_argc(mrb); if (argc != 0) { mrb_argnum_error(mrb, argc, 0, 0); } mrb_int i = mrb_integer(mrb_proc_cfunc_env_get(mrb, 0)); mrb_int len = num_members(mrb, obj); mrb_value *ptr = RSTRUCT_PTR(obj); if (!ptr || len <= i) return mrb_nil_value(); return ptr[i]; } static mrb_sym mrb_id_attrset(mrb_state *mrb, mrb_sym id) { #define ONSTACK_ALLOC_MAX 32 #define ONSTACK_STRLEN_MAX (ONSTACK_ALLOC_MAX - 1) /* '=' character */ char *buf; mrb_int len; char onstack[ONSTACK_ALLOC_MAX]; const char *name = mrb_sym_name_len(mrb, id, &len); if (len > ONSTACK_STRLEN_MAX) { buf = (char*)mrb_malloc(mrb, (size_t)len+1); } else { buf = onstack; } memcpy(buf, name, (size_t)len); buf[len] = '='; mrb_sym mid = mrb_intern(mrb, buf, len+1); if (buf != onstack) { mrb_free(mrb, buf); } return mid; } static mrb_value mrb_struct_set_m(mrb_state *mrb, mrb_value obj) { mrb_int i = mrb_integer(mrb_proc_cfunc_env_get(mrb, 0)); mrb_value val = mrb_get_arg1(mrb); mrb_ary_set(mrb, obj, i, val); return val; } static void make_struct_define_accessors(mrb_state *mrb, mrb_value members, struct RClass *c) { const mrb_value *ptr_members = RARRAY_PTR(members); mrb_int len = RARRAY_LEN(members); int ai = mrb_gc_arena_save(mrb); for (mrb_int i=0; ibasic.c->super = c->c; */ make_struct_define_accessors(mrb, members, c); return nstr; } /* 15.2.18.3.1 */ /* * call-seq: * Struct.new([aString] [, aSym]+, keyword_init: false) -> StructClass * StructClass.new(arg, ...) -> obj * StructClass[arg, ...] -> obj * * Creates a new class, named by *aString*, containing accessor * methods for the given symbols. If the name *aString* is * omitted, an anonymous structure class will be created. Otherwise, * the name of this struct will appear as a constant in class * `Struct`, so it must be unique for all * `Struct`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. * * `Struct::new` returns a new `Class` 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 `nil`. Passing too many * parameters will raise an `ArgumentError`. * * If `keyword_init` is true, the struct will accept keyword * arguments for initialization instead of positional arguments: * * Person = Struct.new(:name, :age, keyword_init: true) * Person.new(name: "Alice", age: 30) * #=> # * * 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") #=> # * * # Create a structure named by its constant * Customer = Struct.new(:name, :address) #=> Customer * Customer.new("Dave", "123 Main") #=> # * * # Create a structure with keyword initialization * User = Struct.new(:id, :email, keyword_init: true) * User.new(id: 1, email: "user@example.com") * #=> # */ static mrb_value mrb_struct_s_def(mrb_state *mrb, mrb_value klass) { mrb_value name = mrb_nil_value(); mrb_value b; const mrb_value *argv; mrb_int argc; mrb_value keyword_init_val = mrb_nil_value(); mrb_get_args(mrb, "*&", &argv, &argc, &b); if (argc == 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given 0, expected 1+)"); } /* Check for keyword_init option in arguments */ if (argc > 0 && mrb_hash_p(argv[argc-1])) { mrb_value options = argv[argc-1]; mrb_value keyword_init_sym = mrb_symbol_value(MRB_SYM(keyword_init)); if (mrb_hash_key_p(mrb, options, keyword_init_sym)) { keyword_init_val = mrb_hash_get(mrb, options, keyword_init_sym); argc--; /* Don't treat the options hash as a member name */ } } const mrb_value *pargv = argv; mrb_int argcnt = argc; if (argc > 0 && !mrb_symbol_p(argv[0])) { /* 1stArgument:!symbol -> name=argv[0] rest=argv[0..n] */ name = argv[0]; pargv++; argcnt--; } mrb_value members = mrb_ary_new_from_values(mrb, argcnt, pargv); for (mrb_int i=0; i 0) { mrb_value keys_str = mrb_ary_join(mrb, invalid_keys, mrb_str_new_lit(mrb, ", ")); mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown keywords: %S", keys_str); } return self; } static mrb_value mrb_struct_initialize(mrb_state *mrb, mrb_value self) { const mrb_value *argv; mrb_int argc; mrb_get_args(mrb, "*", &argv, &argc); mrb_value klass = mrb_obj_value(mrb_obj_class(mrb, self)); mrb_value keyword_init = mrb_iv_get(mrb, klass, MRB_IVSYM(__keyword_init__)); if (mrb_test(keyword_init)) { /* keyword_init: true or other truthy value */ if (argc > 1 || (argc == 1 && !mrb_hash_p(argv[0]))) { mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong arguments, expected keyword arguments"); } mrb_value hash = (argc == 1) ? argv[0] : mrb_hash_new(mrb); return mrb_struct_init_with_keywords(mrb, hash, self); } else if (mrb_equal(mrb, keyword_init, mrb_false_value())) { /* keyword_init: false */ return mrb_struct_init_with_args(mrb, argc, argv, self); } else { /* keyword_init: nil (default) */ if (argc == 1 && mrb_hash_p(argv[0])) { return mrb_struct_init_with_keywords(mrb, argv[0], self); } else { return mrb_struct_init_with_args(mrb, argc, argv, self); } } } /* 15.2.18.4.9 */ /* :nodoc: */ static mrb_value mrb_struct_init_copy(mrb_state *mrb, mrb_value copy) { mrb_value s = mrb_get_arg1(mrb); if (mrb_obj_equal(mrb, copy, s)) return copy; if (!mrb_obj_is_instance_of(mrb, s, mrb_obj_class(mrb, copy))) { mrb_raise(mrb, E_TYPE_ERROR, "wrong argument class"); } if (!mrb_struct_p(s)) { struct_corrupted(mrb); } mrb_ary_replace(mrb, copy, s); return copy; } static mrb_value struct_aref_sym(mrb_state *mrb, mrb_value obj, mrb_sym id) { mrb_value members = struct_members(mrb, obj); const mrb_value *ptr_members = RARRAY_PTR(members); mrb_int len = RARRAY_LEN(members); mrb_value *ptr = RSTRUCT_PTR(obj); mrb_int plen = RARRAY_LEN(obj); for (mrb_int i=0; i= RSTRUCT_LEN(s)) return mrb_nil_value(); return RSTRUCT_PTR(s)[idx]; } /* 15.2.18.4.2 */ /* * call-seq: * struct[symbol] -> anObject * struct[fixnum] -> anObject * * Attribute Reference---Returns the value of the instance variable * named by *symbol*, or indexed (0..length-1) by * *fixnum*. Will raise `NameError` if the named * variable does not exist, or `IndexError` 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" */ static mrb_value mrb_struct_aref(mrb_state *mrb, mrb_value s) { mrb_value idx = mrb_get_arg1(mrb); if (mrb_string_p(idx)) { mrb_sym sym = mrb_intern_str(mrb, idx); idx = mrb_symbol_value(sym); } if (mrb_symbol_p(idx)) { return struct_aref_sym(mrb, s, mrb_symbol(idx)); } return struct_aref_int(mrb, s, mrb_as_int(mrb, idx)); } static mrb_value mrb_struct_aset_sym(mrb_state *mrb, mrb_value s, mrb_sym id, mrb_value val) { mrb_value members = struct_members(mrb, s); mrb_int len = RARRAY_LEN(members); const mrb_value *ptr_members = RARRAY_PTR(members); for (mrb_int i=0; i obj * struct[fixnum] = obj -> obj * * Attribute Assignment---Assigns to the instance variable named by * *symbol* or *fixnum* the value *obj* and * returns it. Will raise a `NameError` if the named * variable does not exist, or an `IndexError` 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" */ static mrb_value mrb_struct_aset(mrb_state *mrb, mrb_value s) { mrb_value idx; mrb_value val; mrb_get_args(mrb, "oo", &idx, &val); if (mrb_string_p(idx)) { mrb_sym sym = mrb_intern_str(mrb, idx); idx = mrb_symbol_value(sym); } if (mrb_symbol_p(idx)) { return mrb_struct_aset_sym(mrb, s, mrb_symbol(idx), val); } mrb_int i = struct_index(mrb, mrb_as_int(mrb, idx), num_members(mrb, s)); mrb_ary_set(mrb, s, i, val); return val; } /* 15.2.18.4.1 */ /* * call-seq: * struct == other_struct -> true or false * * Equality---Returns `true` if *other_struct* is * equal to this one: they must be of the same class as generated by * `Struct::new`, and the values of all instance variables * must be equal (according to `Object#==`). * * 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 = mrb_get_arg1(mrb); if (mrb_obj_equal(mrb, s, s2)) { return mrb_true_value(); } if (mrb_obj_class(mrb, s) != mrb_obj_class(mrb, s2)) { return mrb_false_value(); } if (RSTRUCT_LEN(s) != RSTRUCT_LEN(s2)) { return mrb_false_value(); } /* Check for recursion */ if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_OPSYM(eq), s, s2)) { return mrb_false_value(); } mrb_value *ptr = RSTRUCT_PTR(s); mrb_value *ptr2 = RSTRUCT_PTR(s2); mrb_int len = RSTRUCT_LEN(s); int ai = mrb_gc_arena_save(mrb); for (mrb_int i=0; i true or false * * Two structures are equal if they are the same object, or if all their * fields are equal (using `eql?`). */ static mrb_value mrb_struct_eql(mrb_state *mrb, mrb_value s) { mrb_value s2 = mrb_get_arg1(mrb); mrb_value *ptr, *ptr2; mrb_int i, len; if (mrb_obj_equal(mrb, s, s2)) { return mrb_true_value(); } if (mrb_obj_class(mrb, s) != mrb_obj_class(mrb, s2)) { return mrb_false_value(); } if (RSTRUCT_LEN(s) != RSTRUCT_LEN(s2)) { return mrb_false_value(); } /* Check for recursion */ if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_SYM_Q(eql), s, s2)) { return mrb_false_value(); } ptr = RSTRUCT_PTR(s); ptr2 = RSTRUCT_PTR(s2); len = RSTRUCT_LEN(s); for (i=0; i Integer * struct.size -> Integer * * Returns number of struct members. */ static mrb_value mrb_struct_len(mrb_state *mrb, mrb_value self) { return mrb_fixnum_value(RSTRUCT_LEN(self)); } /* * 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)); } /* * 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 = struct_members(mrb, self); mrb_value ret = mrb_hash_new_capa(mrb, RARRAY_LEN(members)); for (mrb_int i = 0; i < RARRAY_LEN(members); i++) { mrb_hash_set(mrb, ret, RARRAY_PTR(members)[i], mrb_ary_ref(mrb, self, i)); } return ret; } static mrb_value mrb_struct_values_at(mrb_state *mrb, mrb_value self) { mrb_int argc; const mrb_value *argv; mrb_get_args(mrb, "*", &argv, &argc); return mrb_get_values_at(mrb, self, RSTRUCT_LEN(self), argc, argv, struct_aref_int); } /* * call-seq: * struct.to_s -> string * struct.inspect -> string * * Returns a string representation of Data */ static mrb_value mrb_struct_to_s(mrb_state *mrb, mrb_value self) { mrb->c->ci->mid = MRB_SYM(inspect); mrb_value ret = mrb_str_new_lit(mrb, "#"); return ret; } mrb_value members = struct_members(mrb, self); mrb_int mlen = RARRAY_LEN(members); mrb_value *mems = RARRAY_PTR(members); for (mrb_int i=0; i0) mrb_str_cat_lit(mrb, ret, ", "); mrb_str_cat(mrb, ret, name, len); mrb_str_cat_lit(mrb, ret, "="); mrb_str_cat_str(mrb, ret, mrb_inspect(mrb, mrb_ary_ref(mrb, self, i))); mrb_gc_arena_restore(mrb, ai); } mrb_str_cat_lit(mrb, ret, ">"); return ret; } /* * A `Struct` is a convenient way to bundle a number of * attributes together, using accessor methods, without having to write * an explicit class. * * The `Struct` 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 * "*Customer*Class," and we'll show an example instance of that * class as "*Customer*Inst." * * In the descriptions that follow, the parameter *symbol* refers * to a symbol, which is either a quoted string or a * `Symbol` (such as `:name`). */ /* ---------------------------*/ static const mrb_mt_entry struct_rom_entries[] = { MRB_MT_ENTRY(mrb_struct_equal, MRB_OPSYM(eq), MRB_ARGS_REQ(1)), /* 15.2.18.4.1 */ MRB_MT_ENTRY(mrb_struct_aref, MRB_OPSYM(aref), MRB_ARGS_REQ(1)), /* 15.2.18.4.2 */ MRB_MT_ENTRY(mrb_struct_aset, MRB_OPSYM(aset), MRB_ARGS_REQ(2)), /* 15.2.18.4.3 */ MRB_MT_ENTRY(mrb_struct_members, MRB_SYM(members), MRB_ARGS_NONE()), /* 15.2.18.4.6 */ MRB_MT_ENTRY(mrb_struct_initialize, MRB_SYM(initialize), MRB_ARGS_ANY()), /* 15.2.18.4.8 */ MRB_MT_ENTRY(mrb_struct_init_copy, MRB_SYM(initialize_copy), MRB_ARGS_REQ(1) | MRB_MT_PRIVATE), /* 15.2.18.4.9 */ MRB_MT_ENTRY(mrb_struct_eql, MRB_SYM_Q(eql), MRB_ARGS_REQ(1)), /* 15.2.18.4.12(x) */ MRB_MT_ENTRY(mrb_struct_to_s, MRB_SYM(to_s), MRB_ARGS_NONE()), /* 15.2.18.4.11(x) */ MRB_MT_ENTRY(mrb_struct_to_s, MRB_SYM(inspect), MRB_ARGS_NONE()), /* 15.2.18.4.10(x) */ MRB_MT_ENTRY(mrb_struct_len, MRB_SYM(size), MRB_ARGS_NONE()), MRB_MT_ENTRY(mrb_struct_len, MRB_SYM(length), MRB_ARGS_NONE()), MRB_MT_ENTRY(mrb_struct_to_a, MRB_SYM(to_a), MRB_ARGS_NONE()), MRB_MT_ENTRY(mrb_struct_to_a, MRB_SYM(values), MRB_ARGS_NONE()), MRB_MT_ENTRY(mrb_struct_to_h, MRB_SYM(to_h), MRB_ARGS_NONE()), MRB_MT_ENTRY(mrb_struct_values_at, MRB_SYM(values_at), MRB_ARGS_ANY()), }; void mrb_mruby_struct_gem_init(mrb_state* mrb) { struct RClass *st = mrb_define_class_id(mrb, MRB_SYM(Struct), mrb->object_class); MRB_SET_INSTANCE_TT(st, MRB_TT_STRUCT); MRB_UNDEF_ALLOCATOR(st); mrb_define_class_method_id(mrb, st, MRB_SYM(new), mrb_struct_s_def, MRB_ARGS_ANY()); /* 15.2.18.3.1 */ MRB_MT_INIT_ROM(mrb, st, struct_rom_entries); } void mrb_mruby_struct_gem_final(mrb_state* mrb) { }