|
// What OpenStruct should've been.
|
|
#include <string.h>
|
|
#include "ruby.h"
|
|
#include "ruby/internal.h"
|
|
|
|
struct inspect_state_t {
|
|
VALUE ostruct;
|
|
VALUE str;
|
|
_Bool comma;
|
|
};
|
|
|
|
struct inspect_stack_t {
|
|
VALUE ostruct;
|
|
inspect_stack_t* prev;
|
|
};
|
|
|
|
struct equal_state_t {
|
|
VALUE other;
|
|
VALUE result;
|
|
};
|
|
|
|
// This is inaccessible without a GIL.
|
|
static struct inspect_stack_t *root = NULL;
|
|
VALUE rb_cOpenStruct;
|
|
|
|
static int
|
|
initialize_i(VALUE key, VALUE initial, VALUE ostruct)
|
|
{
|
|
ID id = rb_to_id(key);
|
|
rb_attr(rb_singleton_class(ostruct), id, 1, 1, FALSE);
|
|
rb_ivar_set(ostruct, id, initial);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_initialize(int argc, VALUE *argv, VALUE ostruct)
|
|
{
|
|
rb_check_arity(argc, 0, 1);
|
|
rb_check_frozen(obj);
|
|
if (argc && RB_TYPE_P(argv[0], T_HASH)) {
|
|
rb_hash_foreach(argv[0], initialize_i, ostruct);
|
|
}
|
|
}
|
|
|
|
static int
|
|
to_h_i(ID key, VALUE value, VALUE table)
|
|
{
|
|
rb_hash_set(table, ID2SYM(key), value);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_to_h(VALUE ostruct)
|
|
{
|
|
VALUE table = rb_hash_new();
|
|
rb_ivar_foreach(ostruct, to_h_i, table);
|
|
return table;
|
|
}
|
|
|
|
static int
|
|
each_pair_i_fast(ID key, VALUE value, VALUE ostruct)
|
|
{
|
|
VALUE args[] = { ID2SYM(key), value };
|
|
rb_yield_values2(2, args);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
each_pair_i(ID key, VALUE value, VALUE ostruct)
|
|
{
|
|
rb_yield(rb_assoc_new(ID2SYM(key), value));
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
each_pair_size_fn(VALUE ostruct, VALUE _args, VALUE _enum)
|
|
{
|
|
return LONG2FIX(rb_ivar_count(ostruct));
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_each_pair(VALUE ostruct)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(obj, argc, argv, each_pair_size_fn);
|
|
if (rb_block_arity() > 1)
|
|
rb_hash_foreach(hash, each_pair_i_fast, 0);
|
|
else
|
|
rb_hash_foreach(hash, each_pair_i, 0);
|
|
return ostruct;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_method_missing(int argc, VALUE *argv, VALUE ostruct)
|
|
{
|
|
VALUE mid;
|
|
ID id;
|
|
if (argc == 0) return rb_call_super(argc, argv);
|
|
mid = rb_obj_as_string(argv[0]);
|
|
if (RSTRING_END(mid) == RSTRING_PTR(mid) || *RSTRING_END(mid) != '=') {
|
|
if (argc > 2) return rb_call_super(argc, argv);
|
|
} else {
|
|
rb_check_arity(argc - 1, 1, 1);
|
|
rb_check_frozen(obj);
|
|
id = rb_intern(RSTRING_PTR(mid), RSTRING_LEN(mid) - 1);
|
|
rb_attr(rb_singleton_class(ostruct), id, 1, 1, FALSE);
|
|
rb_ivar_set(ostruct, id, argv[1]);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_aref(VALUE ostruct, VALUE key)
|
|
{
|
|
VALUE value = rb_ivar_get(ostruct, rb_to_id(key));
|
|
return RB_TYPE_P(value, Qundef) ? Qnil : value;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_aset(VALUE ostruct, VALUE key, VALUE value)
|
|
{
|
|
ID id = rb_to_id(key);
|
|
rb_check_frozen(obj);
|
|
if (!rb_ivar_defined(ostruct, id)) {
|
|
rb_attr(rb_singleton_class(ostruct), id, 1, 1, FALSE);
|
|
}
|
|
rb_ivar_set(ostruct, id, key);
|
|
return value;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_dig(int argc, VALUE *argv, VALUE ostruct)
|
|
{
|
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
|
do {
|
|
ostruct = rb_ostruct_aref(ostruct, rb_to_id(*argv++));
|
|
if (RB_TYPE_P(ostruct, Qundef)) return Qnil;
|
|
if (RB_NIL_P(ostruct) || !--argc) return ostruct;
|
|
} while (rb_obj_is_kind_of(ostruct, rb_cOpenStruct));
|
|
return rb_funcallv_public(ostruct, rb_intern_const("dig"), argc, argv);
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_delete_field(VALUE ostruct, VALUE name)
|
|
{
|
|
VALUE value;
|
|
ID id;
|
|
char *formatted;
|
|
name = rb_obj_as_string(name);
|
|
rb_check_frozen(obj);
|
|
id = rb_intern_str(name);
|
|
|
|
if (rb_ivar_defined(ostruct, id)) {
|
|
rb_undef(ostruct, id);
|
|
rb_ivar_set(ostruct, id, Qundef);
|
|
} else {
|
|
value = rb_ostruct_inspect(ostruct);
|
|
formatted = malloc(sizeof(char) * (
|
|
// "no field `' in \0" + size of value + size of name
|
|
16 + RSTRING_LEN(name) + RSTRING_LEN(value);
|
|
));
|
|
sprintf(formatted, "no field `%s.*' in %s.*",
|
|
RSTRING_LEN(name), RSTRING_PTR(name),
|
|
RSTRING_LEN(value), RSTRING_PTR(value)
|
|
);
|
|
rb_name_error(id, formatted);
|
|
}
|
|
}
|
|
|
|
static int
|
|
inspect_i(ID key, VALUE value, VALUE arg)
|
|
{
|
|
struct inspect_state_t *state = (inspect_state_t *) arg;
|
|
if (state->comma) rb_str_cat(state->str, ",", 1);
|
|
rb_str_cat(state->str, " ", 1);
|
|
rb_str_append(state->str, key);
|
|
rb_str_cat(state->str, "=", 1);
|
|
rb_str_append(state->str, rb_inspect(value));
|
|
state->comma = TRUE;
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_inspect_begin(VALUE arg)
|
|
{
|
|
struct inspect_state_t *state = (inspect_state_t *) arg;
|
|
rb_ivar_foreach(state->ostruct, inspect_i, (VALUE)state);
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_inspect_ensure(VALUE arg)
|
|
{
|
|
root = root->prev;
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_inspect(VALUE ostruct)
|
|
{
|
|
struct inspect_stack_t *node, next;
|
|
struct rb_ostruct_inspect_state_t state;
|
|
VALUE id = rb_obj_id(ostruct);
|
|
VALUE str = rb_str_new_literal("#<");
|
|
rb_str_append(str, CLASS_OF(ostruct));
|
|
node = root;
|
|
while (node) {
|
|
if (node->ostruct == id) {
|
|
rb_str_cat(str, " ...>", 5);
|
|
return str;
|
|
}
|
|
node = node->prev;
|
|
}
|
|
next = { id, root }
|
|
root = &next;
|
|
state = { ostruct, str, FALSE };
|
|
rb_ensure(
|
|
rb_ostruct_inspect_begin, (VALUE)(&state),
|
|
rb_ostruct_inspect_ensure, Qnil
|
|
);
|
|
rb_str_cat(str, ">", 1);
|
|
return state.str;
|
|
}
|
|
|
|
static int
|
|
equal_i(ID key, VALUE value, VALUE arg)
|
|
{
|
|
equal_state_t *state = (equal_state_t *)arg;
|
|
VALUE other = rb_ivar_get(state->other, key);
|
|
if (!RB_TYPE_P(other, T_UNDEF) && rb_equal(value, other)) {
|
|
return ST_CONTINUE;
|
|
}
|
|
state->result = Qfalse;
|
|
return ST_STOP;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_equal(VALUE ostruct, VALUE other)
|
|
{
|
|
equal_state_t state;
|
|
if (!rb_obj_is_kind_of(other, rb_cOpenStruct)) return Qfalse;
|
|
state = { other, Qtrue };
|
|
rb_ivar_foreach(ostruct, equal_i, (VALUE)(&state));
|
|
return state.result;
|
|
}
|
|
|
|
static int
|
|
eql_i(ID key, VALUE value, VALUE arg)
|
|
{
|
|
equal_state_t *state = (equal_state_t *)arg;
|
|
VALUE other = rb_ivar_get(state->other, key);
|
|
if (!RB_TYPE_P(other, T_UNDEF) && rb_eql(value, other)) return ST_CONTINUE;
|
|
state->result = Qfalse;
|
|
return ST_STOP;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_eql(VALUE ostruct, VALUE other)
|
|
{
|
|
equal_state_t state;
|
|
if (!rb_obj_is_kind_of(other, rb_cOpenStruct)) return Qfalse;
|
|
state = { other, Qtrue };
|
|
rb_ivar_foreach(ostruct, eql_i, (VALUE)(&state));
|
|
return state.result;
|
|
}
|
|
|
|
static int
|
|
rb_ostruct_hash_i(VALUE key, VALUE val, VALUE arg)
|
|
{
|
|
st_index_t *hval = (st_index_t *)arg;
|
|
st_index_t hdata[2];
|
|
|
|
hdata[0] = rb_hash(key);
|
|
hdata[1] = rb_hash(val);
|
|
*hval ^= st_hash(hdata, sizeof(hdata), 0);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
rb_ostruct_hash(VALUE ostruct)
|
|
{
|
|
st_index_t size = (st_index_t)rb_ivar_count(ostruct);
|
|
st_index_t hval = rb_hash_start(size);
|
|
hval = rb_hash_uint(hval, (st_index_t)rb_ostruct_hash);
|
|
if (size) rb_ivar_foreach(hash, rb_ostruct_hash_i, (VALUE)&hval);
|
|
hval = rb_hash_end(hval);
|
|
return ST2FIX(hval);
|
|
}
|
|
|
|
void
|
|
Init_ostruct(void)
|
|
{
|
|
rb_cOpenStruct = rb_define_class("OpenStruct", rb_cObject);
|
|
|
|
rb_define_method(rb_cOpenStruct, "initialize", rb_ostruct_initialize, -1);
|
|
rb_define_method(rb_cOpenStruct, "to_h", rb_ostruct_to_h, 0);
|
|
rb_define_method(rb_cOpenStruct, "each_pair", rb_ostruct_each_pair, 0);
|
|
rb_define_method(rb_cOpenStruct, "marshal_dump", rb_ostruct_to_h, 0);
|
|
rb_define_method(rb_cOpenStruct, "marshal_load", rb_ostruct_initialize, -1);
|
|
rb_define_method(rb_cOpenStruct, "method_missing", rb_ostruct_method_missing, -1);
|
|
rb_define_method(rb_cOpenStruct, "[]", rb_ostruct_aref, 1);
|
|
rb_define_method(rb_cOpenStruct, "[]=", rb_ostruct_aset, 2);
|
|
rb_define_method(rb_cOpenStruct, "dig", rb_ostruct_dig, -1);
|
|
rb_define_method(rb_cOpenStruct, "delete_field", rb_ostruct_delete_field, 1);
|
|
rb_define_method(rb_cOpenStruct, "inspect", rb_ostruct_inspect, 0);
|
|
rb_define_method(rb_cOpenStruct, "to_s", rb_ostruct_inspect, 0);
|
|
rb_define_method(rb_cOpenStruct, "==", rb_ostruct_equal, 1);
|
|
rb_define_method(rb_cOpenStruct, "eql?", rb_ostruct_eql, 1);
|
|
rb_define_method(rb_cOpenStruct, "hash", rb_ostruct_hash, 1);
|
|
}
|