Project

General

Profile

Feature #14555 » ostruct.c

isiahmeadows (Isiah Meadows), 02/27/2018 12:22 AM

 
// 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);
}
(1-1/2)