Project

General

Profile

Feature #14555 ยป ostruct.c

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

 
1
// What OpenStruct should've been.
2
#include <string.h>
3
#include "ruby.h"
4
#include "ruby/internal.h"
5

    
6
struct inspect_state_t {
7
    VALUE ostruct;
8
    VALUE str;
9
    _Bool comma;
10
};
11

    
12
struct inspect_stack_t {
13
    VALUE ostruct;
14
    inspect_stack_t* prev;
15
};
16

    
17
struct equal_state_t {
18
    VALUE other;
19
    VALUE result;
20
};
21

    
22
// This is inaccessible without a GIL.
23
static struct inspect_stack_t *root = NULL;
24
VALUE rb_cOpenStruct;
25

    
26
static int
27
initialize_i(VALUE key, VALUE initial, VALUE ostruct)
28
{
29
    ID id = rb_to_id(key);
30
    rb_attr(rb_singleton_class(ostruct), id, 1, 1, FALSE);
31
    rb_ivar_set(ostruct, id, initial);
32
    return ST_CONTINUE;
33
}
34

    
35
static VALUE
36
rb_ostruct_initialize(int argc, VALUE *argv, VALUE ostruct)
37
{
38
    rb_check_arity(argc, 0, 1);
39
    rb_check_frozen(obj);
40
    if (argc && RB_TYPE_P(argv[0], T_HASH)) {
41
        rb_hash_foreach(argv[0], initialize_i, ostruct);
42
    }
43
}
44

    
45
static int
46
to_h_i(ID key, VALUE value, VALUE table)
47
{
48
    rb_hash_set(table, ID2SYM(key), value);
49
    return ST_CONTINUE;
50
}
51

    
52
static VALUE
53
rb_ostruct_to_h(VALUE ostruct)
54
{
55
    VALUE table = rb_hash_new();
56
    rb_ivar_foreach(ostruct, to_h_i, table);
57
    return table;
58
}
59

    
60
static int
61
each_pair_i_fast(ID key, VALUE value, VALUE ostruct)
62
{
63
    VALUE args[] = { ID2SYM(key), value };
64
    rb_yield_values2(2, args);
65
    return ST_CONTINUE;
66
}
67

    
68
static int
69
each_pair_i(ID key, VALUE value, VALUE ostruct)
70
{
71
    rb_yield(rb_assoc_new(ID2SYM(key), value));
72
    return ST_CONTINUE;
73
}
74

    
75
static VALUE
76
each_pair_size_fn(VALUE ostruct, VALUE _args, VALUE _enum)
77
{
78
    return LONG2FIX(rb_ivar_count(ostruct));
79
}
80

    
81
static VALUE
82
rb_ostruct_each_pair(VALUE ostruct)
83
{
84
    RETURN_SIZED_ENUMERATOR(obj, argc, argv, each_pair_size_fn);
85
    if (rb_block_arity() > 1)
86
        rb_hash_foreach(hash, each_pair_i_fast, 0);
87
    else
88
        rb_hash_foreach(hash, each_pair_i, 0);
89
    return ostruct;
90
}
91

    
92
static VALUE
93
rb_ostruct_method_missing(int argc, VALUE *argv, VALUE ostruct)
94
{
95
    VALUE mid;
96
    ID id;
97
    if (argc == 0) return rb_call_super(argc, argv);
98
    mid = rb_obj_as_string(argv[0]);
99
    if (RSTRING_END(mid) == RSTRING_PTR(mid) || *RSTRING_END(mid) != '=') {
100
        if (argc > 2) return rb_call_super(argc, argv);
101
    } else {
102
        rb_check_arity(argc - 1, 1, 1);
103
        rb_check_frozen(obj);
104
        id = rb_intern(RSTRING_PTR(mid), RSTRING_LEN(mid) - 1);
105
        rb_attr(rb_singleton_class(ostruct), id, 1, 1, FALSE);
106
        rb_ivar_set(ostruct, id, argv[1]);
107
    }
108
    return Qnil;
109
}
110

    
111
static VALUE
112
rb_ostruct_aref(VALUE ostruct, VALUE key)
113
{
114
    VALUE value = rb_ivar_get(ostruct, rb_to_id(key));
115
    return RB_TYPE_P(value, Qundef) ? Qnil : value;
116
}
117

    
118
static VALUE
119
rb_ostruct_aset(VALUE ostruct, VALUE key, VALUE value)
120
{
121
    ID id = rb_to_id(key);
122
    rb_check_frozen(obj);
123
    if (!rb_ivar_defined(ostruct, id)) {
124
        rb_attr(rb_singleton_class(ostruct), id, 1, 1, FALSE);
125
    }
126
    rb_ivar_set(ostruct, id, key);
127
    return value;
128
}
129

    
130
static VALUE
131
rb_ostruct_dig(int argc, VALUE *argv, VALUE ostruct)
132
{
133
    rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
134
    do {
135
        ostruct = rb_ostruct_aref(ostruct, rb_to_id(*argv++));
136
        if (RB_TYPE_P(ostruct, Qundef)) return Qnil;
137
        if (RB_NIL_P(ostruct) || !--argc) return ostruct;
138
    } while (rb_obj_is_kind_of(ostruct, rb_cOpenStruct));
139
    return rb_funcallv_public(ostruct, rb_intern_const("dig"), argc, argv);
140
}
141

    
142
static VALUE
143
rb_ostruct_delete_field(VALUE ostruct, VALUE name)
144
{
145
    VALUE value;
146
    ID id;
147
    char *formatted;
148
    name = rb_obj_as_string(name);
149
    rb_check_frozen(obj);
150
    id = rb_intern_str(name);
151

    
152
    if (rb_ivar_defined(ostruct, id)) {
153
        rb_undef(ostruct, id);
154
        rb_ivar_set(ostruct, id, Qundef);
155
    } else {
156
        value = rb_ostruct_inspect(ostruct);
157
        formatted = malloc(sizeof(char) * (
158
            // "no field `' in \0" + size of value + size of name
159
            16 + RSTRING_LEN(name) + RSTRING_LEN(value);
160
        ));
161
        sprintf(formatted, "no field `%s.*' in %s.*",
162
            RSTRING_LEN(name), RSTRING_PTR(name),
163
            RSTRING_LEN(value), RSTRING_PTR(value)
164
        );
165
        rb_name_error(id, formatted);
166
    }
167
}
168

    
169
static int
170
inspect_i(ID key, VALUE value, VALUE arg)
171
{
172
    struct inspect_state_t *state = (inspect_state_t *) arg;
173
    if (state->comma) rb_str_cat(state->str, ",", 1);
174
    rb_str_cat(state->str, " ", 1);
175
    rb_str_append(state->str, key);
176
    rb_str_cat(state->str, "=", 1);
177
    rb_str_append(state->str, rb_inspect(value));
178
    state->comma = TRUE;
179
    return ST_CONTINUE;
180
}
181

    
182
static VALUE
183
rb_ostruct_inspect_begin(VALUE arg)
184
{
185
    struct inspect_state_t *state = (inspect_state_t *) arg;
186
    rb_ivar_foreach(state->ostruct, inspect_i, (VALUE)state);
187
    return Qnil;
188
}
189

    
190
static VALUE
191
rb_ostruct_inspect_ensure(VALUE arg)
192
{
193
    root = root->prev;
194
    return Qnil;
195
}
196

    
197
static VALUE
198
rb_ostruct_inspect(VALUE ostruct)
199
{
200
    struct inspect_stack_t *node, next;
201
    struct rb_ostruct_inspect_state_t state;
202
    VALUE id = rb_obj_id(ostruct);
203
    VALUE str = rb_str_new_literal("#<");
204
    rb_str_append(str, CLASS_OF(ostruct));
205
    node = root;
206
    while (node) {
207
        if (node->ostruct == id) {
208
            rb_str_cat(str, " ...>", 5);
209
            return str;
210
        }
211
        node = node->prev;
212
    }
213
    next = { id, root }
214
    root = &next;
215
    state = { ostruct, str, FALSE };
216
    rb_ensure(
217
        rb_ostruct_inspect_begin, (VALUE)(&state),
218
        rb_ostruct_inspect_ensure, Qnil
219
    );
220
    rb_str_cat(str, ">", 1);
221
    return state.str;
222
}
223

    
224
static int
225
equal_i(ID key, VALUE value, VALUE arg)
226
{
227
    equal_state_t *state = (equal_state_t *)arg;
228
    VALUE other = rb_ivar_get(state->other, key);
229
    if (!RB_TYPE_P(other, T_UNDEF) && rb_equal(value, other)) {
230
        return ST_CONTINUE;
231
    }
232
    state->result = Qfalse;
233
    return ST_STOP;
234
}
235

    
236
static VALUE
237
rb_ostruct_equal(VALUE ostruct, VALUE other)
238
{
239
    equal_state_t state;
240
    if (!rb_obj_is_kind_of(other, rb_cOpenStruct)) return Qfalse;
241
    state = { other, Qtrue };
242
    rb_ivar_foreach(ostruct, equal_i, (VALUE)(&state));
243
    return state.result;
244
}
245

    
246
static int
247
eql_i(ID key, VALUE value, VALUE arg)
248
{
249
    equal_state_t *state = (equal_state_t *)arg;
250
    VALUE other = rb_ivar_get(state->other, key);
251
    if (!RB_TYPE_P(other, T_UNDEF) && rb_eql(value, other)) return ST_CONTINUE;
252
    state->result = Qfalse;
253
    return ST_STOP;
254
}
255

    
256
static VALUE
257
rb_ostruct_eql(VALUE ostruct, VALUE other)
258
{
259
    equal_state_t state;
260
    if (!rb_obj_is_kind_of(other, rb_cOpenStruct)) return Qfalse;
261
    state = { other, Qtrue };
262
    rb_ivar_foreach(ostruct, eql_i, (VALUE)(&state));
263
    return state.result;
264
}
265

    
266
static int
267
rb_ostruct_hash_i(VALUE key, VALUE val, VALUE arg)
268
{
269
    st_index_t *hval = (st_index_t *)arg;
270
    st_index_t hdata[2];
271

    
272
    hdata[0] = rb_hash(key);
273
    hdata[1] = rb_hash(val);
274
    *hval ^= st_hash(hdata, sizeof(hdata), 0);
275
    return ST_CONTINUE;
276
}
277

    
278
static VALUE
279
rb_ostruct_hash(VALUE ostruct)
280
{
281
    st_index_t size = (st_index_t)rb_ivar_count(ostruct);
282
    st_index_t hval = rb_hash_start(size);
283
    hval = rb_hash_uint(hval, (st_index_t)rb_ostruct_hash);
284
    if (size) rb_ivar_foreach(hash, rb_ostruct_hash_i, (VALUE)&hval);
285
    hval = rb_hash_end(hval);
286
    return ST2FIX(hval);
287
}
288

    
289
void
290
Init_ostruct(void)
291
{
292
    rb_cOpenStruct = rb_define_class("OpenStruct", rb_cObject);
293

    
294
    rb_define_method(rb_cOpenStruct, "initialize", rb_ostruct_initialize, -1);
295
    rb_define_method(rb_cOpenStruct, "to_h", rb_ostruct_to_h, 0);
296
    rb_define_method(rb_cOpenStruct, "each_pair", rb_ostruct_each_pair, 0);
297
    rb_define_method(rb_cOpenStruct, "marshal_dump", rb_ostruct_to_h, 0);
298
    rb_define_method(rb_cOpenStruct, "marshal_load", rb_ostruct_initialize, -1);
299
    rb_define_method(rb_cOpenStruct, "method_missing", rb_ostruct_method_missing, -1);
300
    rb_define_method(rb_cOpenStruct, "[]", rb_ostruct_aref, 1);
301
    rb_define_method(rb_cOpenStruct, "[]=", rb_ostruct_aset, 2);
302
    rb_define_method(rb_cOpenStruct, "dig", rb_ostruct_dig, -1);
303
    rb_define_method(rb_cOpenStruct, "delete_field", rb_ostruct_delete_field, 1);
304
    rb_define_method(rb_cOpenStruct, "inspect", rb_ostruct_inspect, 0);
305
    rb_define_method(rb_cOpenStruct, "to_s", rb_ostruct_inspect, 0);
306
    rb_define_method(rb_cOpenStruct, "==", rb_ostruct_equal, 1);
307
    rb_define_method(rb_cOpenStruct, "eql?", rb_ostruct_eql, 1);
308
    rb_define_method(rb_cOpenStruct, "hash", rb_ostruct_hash, 1);
309
}