diff --git a/mrbgems/mruby-data/mrbgem.rake b/mrbgems/mruby-data/mrbgem.rake new file mode 100644 index 000000000..ea8979202 --- /dev/null +++ b/mrbgems/mruby-data/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('mruby-data') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'standard Data class' +end diff --git a/mrbgems/mruby-data/src/data.c b/mrbgems/mruby-data/src/data.c new file mode 100644 index 000000000..28b703105 --- /dev/null +++ b/mrbgems/mruby-data/src/data.c @@ -0,0 +1,422 @@ +/* +** data.c - Data class +** +** See Copyright Notice in mruby.h +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RDATA_LEN(st) RARRAY_LEN(st) +#define RDATA_PTR(st) RARRAY_PTR(st) + +#define mrb_dataobj_p(o) (mrb_type(o) == MRB_TT_STRUCT) + +static struct RClass * +data_class(mrb_state *mrb) +{ + return mrb_class_get_id(mrb, MRB_SYM(Data)); +} + +static void +data_corrupted(mrb_state *mrb) +{ + mrb_raise(mrb, E_TYPE_ERROR, "corrupted data"); +} + +static mrb_value +data_s_members(mrb_state *mrb, struct RClass *c) +{ + struct RClass* sclass = data_class(mrb); + mrb_value mem; + + for (;;) { + mem = mrb_iv_get(mrb, mrb_obj_value(c), MRB_SYM(__members__)); + if (!mrb_nil_p(mem)) { + if (!mrb_array_p(mem)) { + data_corrupted(mrb); + } + return mem; + } + c = c->super; + if (c == sclass || c == 0) { + mrb_raise(mrb, E_TYPE_ERROR, "uninitialized data"); + } + } +} + +static mrb_value +data_members(mrb_state *mrb, mrb_value obj) +{ + if (!mrb_dataobj_p(obj) || RDATA_LEN(obj) == 0) { + data_corrupted(mrb); + } + mrb_value members = data_s_members(mrb, mrb_obj_class(mrb, obj)); + if (RDATA_LEN(obj) != RARRAY_LEN(members)) { + mrb_raisef(mrb, E_TYPE_ERROR, + "data size differs (%i required %i given)", + RARRAY_LEN(members), RDATA_LEN(obj)); + } + return members; +} + +static mrb_value +mrb_data_s_members_m(mrb_state *mrb, mrb_value klass) +{ + mrb_value members, ary; + + members = data_s_members(mrb, mrb_class_ptr(klass)); + ary = mrb_ary_new_capa(mrb, RARRAY_LEN(members)); + mrb_ary_replace(mrb, ary, members); + return ary; +} + +/* + * call-seq: + * data.members -> array + * + * Returns an array of strings representing the names of the instance + * variables. + * + * Customer = Data.define(:name, :address, :zip) + * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) + * joe.members #=> [:name, :address, :zip] + */ + +static mrb_value +mrb_data_members(mrb_state *mrb, mrb_value obj) +{ + return mrb_data_s_members_m(mrb, mrb_obj_value(mrb_obj_class(mrb, obj))); +} + +static mrb_value +mrb_data_ref(mrb_state *mrb, mrb_value obj) +{ + mrb_int i = mrb_integer(mrb_proc_cfunc_env_get(mrb, 0)); + mrb_value *ptr = RDATA_PTR(obj); + + if (!ptr) return mrb_nil_value(); + return ptr[i]; +} + +static void +make_data_define_accessors(mrb_state *mrb, mrb_value members, struct RClass *c) +{ + const mrb_value *ptr_members = RARRAY_PTR(members); + mrb_int i; + mrb_int len = RARRAY_LEN(members); + int ai = mrb_gc_arena_save(mrb); + + for (i=0; ibasic.c->super = c->c; */ + make_data_define_accessors(mrb, members, c); + return nstr; +} + +/* + * call-seq: + * 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 + * Structs 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. + * + * The remaining methods listed in this section (class and instance) + * are defined for this generated class. + * + * # Create a structure named by its constant + * Customer = Struct.new(:name, :address) #=> Customer + * Customer.new("Dave", "123 Main") #=> # + */ +static mrb_value +mrb_data_s_def(mrb_state *mrb, mrb_value klass) +{ + mrb_value rest; + mrb_int i; + mrb_value b, st; + mrb_sym id; + const mrb_value *argv; + mrb_int argc; + + mrb_get_args(mrb, "*&", &argv, &argc, &b); + if (argc == 0) { /* special case to avoid crash */ + mrb_argnum_error(mrb, argc, 1, -1); + } + else { + rest = mrb_ary_new_from_values(mrb, argc, argv); + for (i=0; i true or false + * + * Equality---Returns true if other_data is + * equal to this one: they must be of the same class as generated by + * Struct::define, and all values of must be equal + * (according to Object#==). + * + * Customer = Data.define(:name, :address, :zip) + * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) + * joe2 = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) + * jane = Customer.new("Jane Doe", "456 Elm, Anytown NC", 12345) + * joe == joe2 #=> true + * joe == jane #=> false + */ + +static mrb_value +mrb_data_equal(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 (RDATA_LEN(s) != RDATA_LEN(s2)) { + return mrb_false_value(); + } + ptr = RDATA_PTR(s); + ptr2 = RDATA_PTR(s2); + len = RDATA_LEN(s); + for (i=0; i true or false + * + * Two structures are equal if they are the same object, or if all their + * fields are equal (using Object#eql?). + */ +static mrb_value +mrb_data_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 (RDATA_LEN(s) != RDATA_LEN(s2)) { + return mrb_false_value(); + } + ptr = RDATA_PTR(s); + ptr2 = RDATA_PTR(s2); + len = RDATA_LEN(s); + for (i=0; i hash + * + * Create a hash from member names and values. + */ +static mrb_value +mrb_data_to_h(mrb_state *mrb, mrb_value self) +{ + mrb_value members, ret; + mrb_int i; + + members = data_members(mrb, self); + 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], RDATA_PTR(self)[i]); + } + + return ret; +} + +/* + * call-seq: + * data.to_s -> string + * data.inspect -> string + * + * Returns a string representation of Data + */ +static mrb_value +mrb_data_to_s(mrb_state *mrb, mrb_value self) +{ + mrb_value members, ret; + mrb_value *ptr; + mrb_int i; + + members = data_members(mrb, self); + ret = mrb_str_new_lit(mrb, "#0) 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, ptr[i])); + } + mrb_str_cat_lit(mrb, ret, ">"); + + return ret; +} + +/* + * A Data 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 + * "CustomerClass," and we'll show an example instance of that + * class as "CustomerInst." + * + * In the descriptions that follow, the parameter symbol refers + * to a symbol, which is either a quoted string or a + * Symbol (such as :name). + */ +void +mrb_mruby_data_gem_init(mrb_state* mrb) +{ + struct RClass *d; + d = mrb_define_class(mrb, "Data", mrb->object_class); + MRB_SET_INSTANCE_TT(d, MRB_TT_STRUCT); + + mrb_undef_class_method(mrb, d, "new"); + mrb_define_class_method(mrb, d, "define", mrb_data_s_def, MRB_ARGS_ANY()); + + mrb_define_method(mrb, d, "==", mrb_data_equal, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, d, "members", mrb_data_members, MRB_ARGS_NONE()); + mrb_define_method(mrb, d, "initialize", mrb_data_initialize, MRB_ARGS_ANY()); + mrb_define_method(mrb, d, "initialize_copy", mrb_data_init_copy, MRB_ARGS_ANY()); + mrb_define_method(mrb, d, "eql?", mrb_data_eql, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, d, "to_h", mrb_data_to_h, MRB_ARGS_NONE()); + mrb_define_method(mrb, d, "to_s", mrb_data_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, d, "inspect", mrb_data_to_s, MRB_ARGS_NONE()); +} + +void +mrb_mruby_data_gem_final(mrb_state* mrb) +{ +} diff --git a/mrbgems/mruby-data/test/data.rb b/mrbgems/mruby-data/test/data.rb new file mode 100644 index 000000000..e54be06c9 --- /dev/null +++ b/mrbgems/mruby-data/test/data.rb @@ -0,0 +1,76 @@ +## +# Struct ISO Test + +assert('Data') do + assert_equal Class, Data.class +end + +assert('Data.define') do + c = Data.define(:m1, :m2) + assert_equal Data, c.superclass + assert_equal [:m1, :m2], c.members +end + +assert('Data#==') do + c = Data.define(:m1, :m2) + cc1 = c.new(1,2) + cc2 = c.new(1,2) + assert_true cc1 == cc2 +end + +assert('Data#members') do + c = Data.define(:m1, :m2) + assert_equal [:m1, :m2], c.new(1,2).members +end + +assert('wrong struct arg count') do + c = Data.define(:m1) + assert_raise ArgumentError do + cc = c.new(1,2,3) + end +end + +assert('data dup') do + c = Data.define(:m1, :m2, :m3, :m4, :m5) + cc = c.new(1,2,3,4,5) + assert_nothing_raised { + assert_equal(cc, cc.dup) + } +end + +assert('Data inspect') do + c = Data.define(:m1, :m2, :m3, :m4, :m5) + cc = c.new(1,2,3,4,5) + assert_equal "#", cc.inspect +end + +assert('Data#to_h') do + s = Data.define(:white, :red, :green).new('ruuko', 'yuzuki', 'hitoe') + assert_equal({:white => 'ruuko', :red => 'yuzuki', :green => 'hitoe'}) { s.to_h } +end + +assert("Data.define does not allow array") do + assert_raise(TypeError) do + Data.define("Test", [:a]) + end +end + +assert("Data.define generates subclass of Data") do + begin + original_struct = Data + Data = String + assert_equal original_struct, original_struct.define(:foo).superclass + ensure + Data = original_struct + end +end + +assert 'Data#freeze' do + c = Data.define(:m) + + o = c.new(:test) + assert_equal :test, o.m + assert_nothing_raised { + o.freeze + } +end diff --git a/mrbgems/stdlib-ext.gembox b/mrbgems/stdlib-ext.gembox index 2177dca36..29157dc67 100644 --- a/mrbgems/stdlib-ext.gembox +++ b/mrbgems/stdlib-ext.gembox @@ -13,6 +13,9 @@ MRuby::GemBox.new do |conf| # Use standard Struct class conf.gem :core => "mruby-struct" + # Use standard Data class + conf.gem :core => "mruby-data" + # Use Random class conf.gem :core => "mruby-random" end