Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 611 lines (545 sloc) 15.041 kB
5f7de4c finished Hash refactoring
Laurent Sansonetti authored
1 /*
2 * MacRuby extensions to NSDictionary.
3 *
4 * This file is covered by the Ruby license. See COPYING for more details.
5 *
6 * Copyright (C) 2010, Apple Inc. All rights reserved.
7 */
8
9 #import <Foundation/Foundation.h>
10
11 #include "ruby/ruby.h"
12 #include "ruby/node.h"
13 #include "objc.h"
14 #include "vm.h"
15
16 VALUE rb_cHash;
17 VALUE rb_cNSHash;
18 VALUE rb_cNSMutableHash;
19 static VALUE rb_cCFHash;
20
21 static id
22 to_hash(id hash)
23 {
24 return (id)rb_convert_type((VALUE)hash, T_HASH, "Hash", "to_hash");
25 }
26
27 static id
28 nshash_dup(id rcv, SEL sel)
29 {
30 id dup = [rcv mutableCopy];
31 if (OBJ_TAINTED(rcv)) {
32 OBJ_TAINT(dup);
33 }
34 return dup;
35 }
36
37 static id
38 nshash_clone(id rcv, SEL sel)
39 {
40 id clone = nshash_dup(rcv, 0);
41 if (OBJ_FROZEN(rcv)) {
42 OBJ_FREEZE(clone);
43 }
44 return clone;
45 }
46
47 static id
48 nshash_rehash(id rcv, SEL sel)
49 {
50 NSArray *keys = [rcv allKeys];
51 NSArray *values = [rcv allValues];
52 assert([keys count] == [values count]);
53 [rcv removeAllObjects];
54 for (unsigned i = 0, count = [keys count]; i < count; i++) {
55 [rcv setObject:[values objectAtIndex:i] forKey:[keys objectAtIndex:i]];
56 }
57 return rcv;
58 }
59
60 static id
61 nshash_to_hash(id rcv, SEL sel)
62 {
63 return rcv;
64 }
65
66 static id
67 nshash_to_a(id rcv, SEL sel)
68 {
69 NSMutableArray *ary = [NSMutableArray new];
70 for (id key in rcv) {
71 id value = [rcv objectForKey:key];
72 [ary addObject:[NSArray arrayWithObjects:key, value, nil]];
73 }
74 return ary;
75 }
76
77 static id
78 nshash_inspect(id rcv, SEL sel)
79 {
80 NSMutableString *str = [NSMutableString new];
81 [str appendString:@"{"];
82 for (id key in rcv) {
83 if ([str length] > 1) {
84 [str appendString:@", "];
85 }
86 id value = [rcv objectForKey:key];
87 [str appendString:(NSString *)rb_inspect(OC2RB(key))];
88 [str appendString:@"=>"];
89 [str appendString:(NSString *)rb_inspect(OC2RB(value))];
90 }
91 [str appendString:@"}"];
92 return str;
93 }
94
95 static VALUE
96 nshash_equal(id rcv, SEL sel, id other)
97 {
98 return [rcv isEqualToDictionary:other] ? Qtrue : Qfalse;
99 }
100
101 static VALUE
102 nshash_aref(id rcv, SEL sel, VALUE key)
103 {
104 return OC2RB([rcv objectForKey:RB2OC(key)]);
105 }
106
107 static VALUE
108 nshash_aset(id rcv, SEL sel, VALUE key, VALUE val)
109 {
110 [rcv setObject:RB2OC(val) forKey:RB2OC(key)];
111 return val;
112 }
113
114 static VALUE
115 nshash_fetch(id rcv, SEL sel, int argc, VALUE *argv)
116 {
117 VALUE key, if_none;
118 rb_scan_args(argc, argv, "11", &key, &if_none);
119
120 const bool block_given = rb_block_given_p();
121 if (block_given && argc == 2) {
122 rb_warn("block supersedes default value argument");
123 }
124
125 id value = [rcv objectForKey:RB2OC(key)];
126 if (value != nil) {
127 return OC2RB(value);
128 }
129 if (block_given) {
130 return rb_yield(key);
131 }
132 if (argc == 1) {
133 rb_raise(rb_eKeyError, "key not found");
134 }
135 return if_none;
136 }
137
138 static VALUE
139 nshash_default(id rcv, SEL sel, int argc, VALUE *argv)
140 {
141 // TODO
142 return Qnil;
143 }
144
145 static VALUE
146 nshash_set_default(id rcv, SEL sel, VALUE default_value)
147 {
148 // TODO
149 return Qnil;
150 }
151
152 static VALUE
153 nshash_default_proc(id rcv, SEL sel)
154 {
155 // Default procs are never possible with NSDictionaries.
156 return Qnil;
157 }
158
159 static VALUE
160 nshash_key(id rcv, SEL sel, VALUE value)
161 {
162 NSArray *keys = [rcv allKeysForObject:RB2OC(value)];
163 if ([keys count] > 0) {
164 return OC2RB([keys objectAtIndex:0]);
165 }
166 return Qnil;
167 }
168
169 static VALUE
170 nshash_index(id rcv, SEL sel, VALUE value)
171 {
172 rb_warn("Hash#index is deprecated; use Hash#key");
173 return nshash_key(rcv, 0, value);
174 }
175
176 static VALUE
177 nshash_size(id rcv, SEL sel)
178 {
179 return LONG2FIX([rcv count]);
180 }
181
182 static VALUE
183 nshash_empty(id rcv, SEL sel)
184 {
185 return [rcv count] == 0 ? Qtrue : Qfalse;
186 }
187
188 static VALUE
189 nshash_each_value(id rcv, SEL sel)
190 {
191 RETURN_ENUMERATOR(rcv, 0, 0);
192 for (id key in rcv) {
193 rb_yield(OC2RB([rcv objectForKey:key]));
194 }
195 return (VALUE)rcv;
196 }
197
198 static VALUE
199 nshash_each_key(id rcv, SEL sel)
200 {
201 RETURN_ENUMERATOR(rcv, 0, 0);
202 for (id key in rcv) {
203 rb_yield(OC2RB(key));
204 }
205 return (VALUE)rcv;
206 }
207
208 static VALUE
209 nshash_each_pair(id rcv, SEL sel)
210 {
211 RETURN_ENUMERATOR(rcv, 0, 0);
212 for (id key in rcv) {
213 id value = [rcv objectForKey:key];
214 rb_yield(rb_assoc_new(OC2RB(key), OC2RB(value)));
215 }
216 return (VALUE)rcv;
217 }
218
219 static id
220 nshash_keys(id rcv, SEL sel)
221 {
222 return [[rcv allKeys] mutableCopy];
223 }
224
225 static id
226 nshash_values(id rcv, SEL sel)
227 {
228 return [[rcv allValues] mutableCopy];
229 }
230
231 static id
232 nshash_values_at(id rcv, SEL sel, int argc, VALUE *argv)
233 {
234 NSMutableArray *ary = [NSMutableArray new];
235 for (int i = 0; i < argc; i++) {
236 id value = [rcv objectForKey:RB2OC(argv[i])];
237 [ary addObject:value];
238 }
239 return ary;
240 }
241
242 static VALUE
243 nshash_shift(id rcv, SEL sel)
244 {
245 if ([rcv count] > 0) {
246 id key = [[rcv keyEnumerator] nextObject];
247 assert(key != NULL);
248 id value = [rcv objectForKey:key];
249 [rcv removeObjectForKey:key];
250 return rb_assoc_new(OC2RB(key), OC2RB(value));
251 }
252 return nshash_default(rcv, 0, 0, NULL);
253 }
254
255 static VALUE
256 nshash_delete(id rcv, SEL sel, VALUE key)
257 {
258 id ockey = RB2OC(key);
259 id value = [rcv objectForKey:ockey];
260 if (value != nil) {
261 [rcv removeObjectForKey:ockey];
262 return OC2RB(value);
263 }
264 if (rb_block_given_p()) {
265 return rb_yield(key);
266 }
267 return Qnil;
268 }
269
270 static VALUE
271 nshash_delete_if(id rcv, SEL sel)
272 {
273 RETURN_ENUMERATOR(rcv, 0, 0);
274 NSMutableArray *ary = [NSMutableArray new];
275 for (id key in rcv) {
276 id value = [rcv objectForKey:key];
277 if (RTEST(rb_yield_values(2, OC2RB(key), OC2RB(value)))) {
278 [ary addObject:key];
279 }
280 }
281 [rcv removeObjectsForKeys:ary];
282 return (VALUE)rcv;
283 }
284
285 static VALUE
286 nshash_select(id rcv, SEL sel)
287 {
288 RETURN_ENUMERATOR(rcv, 0, 0);
289 NSMutableDictionary *dict = [NSMutableDictionary new];
290 for (id key in rcv) {
291 id value = [rcv objectForKey:key];
292 if (RTEST(rb_yield_values(2, OC2RB(key), OC2RB(value)))) {
293 [dict setObject:value forKey:key];
294 }
295 }
296 return (VALUE)dict;
297 }
298
299 static VALUE
300 nshash_reject(id rcv, SEL sel)
301 {
302 RETURN_ENUMERATOR(rcv, 0, 0);
303 return nshash_delete_if([rcv mutableCopy], 0);
304 }
305
306 static VALUE
307 nshash_reject_bang(id rcv, SEL sel)
308 {
309 RETURN_ENUMERATOR(rcv, 0, 0);
310 unsigned size = [rcv count];
311 nshash_delete_if(rcv, 0);
312 return size != [rcv count] ? (VALUE)rcv : Qnil;
313 }
314
315 static id
316 nshash_clear(id rcv, SEL sel)
317 {
318 [rcv removeAllObjects];
319 return rcv;
320 }
321
322 static id
323 nshash_update(id rcv, SEL sel, id hash)
324 {
325 hash = to_hash(hash);
326 if (rb_block_given_p()) {
327 for (id key in hash) {
328 id value = [hash objectForKey:key];
329 id old_value = [rcv objectForKey:key];
330 if (old_value != nil) {
331 value = RB2OC(rb_yield_values(3, OC2RB(key), OC2RB(old_value),
332 OC2RB(value)));
333 }
334 [rcv setObject:value forKey:key];
335 }
336 }
337 else {
338 for (id key in hash) {
339 id value = [hash objectForKey:key];
340 [rcv setObject:value forKey:key];
341 }
342 }
343 return rcv;
344 }
345
346 static id
347 nshash_merge(id rcv, SEL sel, id hash)
348 {
349 return nshash_update([rcv mutableCopy], 0, hash);
350 }
351
352 static id
353 nshash_replace(id rcv, SEL sel, id hash)
354 {
355 hash = to_hash(hash);
356 [rcv setDictionary:hash];
357 return rcv;
358 }
359
360 static VALUE
361 nshash_assoc(id rcv, SEL sel, VALUE obj)
362 {
363 for (id key in rcv) {
364 if (rb_equal(OC2RB(key), obj)) {
365 id value = [rcv objectForKey:key];
366 return rb_assoc_new(obj, OC2RB(value));
367 }
368 }
369 return Qnil;
370 }
371
372 static VALUE
373 nshash_rassoc(id rcv, SEL sel, VALUE obj)
374 {
375 for (id key in rcv) {
376 id value = [rcv objectForKey:key];
377 if (rb_equal(OC2RB(value), obj)) {
378 return rb_assoc_new(OC2RB(key), obj);
379 }
380 }
381 return Qnil;
382 }
383
384 static id
385 nshash_flatten(id rcv, SEL sel, int argc, VALUE *argv)
386 {
387 id ary = nshash_to_a(rcv, 0);
388 VALUE tmp;
389 if (argc == 0) {
390 argc = 1;
391 tmp = INT2FIX(1);
392 argv = &tmp;
393 }
394 rb_vm_call((VALUE)ary, sel_registerName("flatten!:"), argc, argv, false);
395 return ary;
396 }
397
398 static VALUE
399 nshash_has_key(id rcv, SEL sel, VALUE key)
400 {
401 return [rcv objectForKey:RB2OC(key)] == nil ? Qfalse : Qtrue;
402 }
403
404 static VALUE
405 nshash_has_value(id rcv, SEL sel, VALUE value)
406 {
407 return [[rcv allKeysForObject:RB2OC(value)] count] == 0 ? Qfalse : Qtrue;
408 }
409
410 static id
411 nshash_compare_by_id(id rcv, SEL sel)
412 {
413 // Not implemented.
414 return rcv;
415 }
416
417 static VALUE
418 nshash_compare_by_id_p(id rcv, SEL sel)
419 {
420 // Not implemented.
421 return Qfalse;
422 }
423
424 void
425 Init_NSDictionary(void)
426 {
427 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
428 rb_cCFHash = (VALUE)objc_getClass("NSCFDictionary");
429 #else
430 rb_cCFHash = (VALUE)objc_getClass("__NSCFDictionary");
431 #endif
432 assert(rb_cCFHash != 0);
433 rb_cNSHash = (VALUE)objc_getClass("NSDictionary");
434 assert(rb_cNSHash != 0);
435 rb_cHash = rb_cNSHash;
436 rb_cNSMutableHash = (VALUE)objc_getClass("NSMutableDictionary");
437 assert(rb_cNSMutableHash != 0);
438
439 rb_include_module(rb_cHash, rb_mEnumerable);
440
441 rb_objc_define_method(rb_cHash, "dup", nshash_dup, 0);
442 rb_objc_define_method(rb_cHash, "clone", nshash_clone, 0);
443 rb_objc_define_method(rb_cHash, "rehash", nshash_rehash, 0);
444 rb_objc_define_method(rb_cHash, "to_hash", nshash_to_hash, 0);
445 rb_objc_define_method(rb_cHash, "to_a", nshash_to_a, 0);
446 rb_objc_define_method(rb_cHash, "to_s", nshash_inspect, 0);
447 rb_objc_define_method(rb_cHash, "inspect", nshash_inspect, 0);
448 rb_objc_define_method(rb_cHash, "==", nshash_equal, 1);
449 rb_objc_define_method(rb_cHash, "eql?", nshash_equal, 1);
450 rb_objc_define_method(rb_cHash, "[]", nshash_aref, 1);
451 rb_objc_define_method(rb_cHash, "[]=", nshash_aset, 2);
452 rb_objc_define_method(rb_cHash, "fetch", nshash_fetch, -1);
453 rb_objc_define_method(rb_cHash, "store", nshash_aset, 2);
454 rb_objc_define_method(rb_cHash, "default", nshash_default, -1);
455 rb_objc_define_method(rb_cHash, "default=", nshash_set_default, 1);
456 rb_objc_define_method(rb_cHash, "default_proc", nshash_default_proc, 0);
457 rb_objc_define_method(rb_cHash, "key", nshash_key, 1);
458 rb_objc_define_method(rb_cHash, "index", nshash_index, 1);
459 rb_objc_define_method(rb_cHash, "size", nshash_size, 0);
460 rb_objc_define_method(rb_cHash, "length", nshash_size, 0);
461 rb_objc_define_method(rb_cHash, "empty?", nshash_empty, 0);
462 rb_objc_define_method(rb_cHash, "each_value", nshash_each_value, 0);
463 rb_objc_define_method(rb_cHash, "each_key", nshash_each_key, 0);
464 rb_objc_define_method(rb_cHash, "each_pair", nshash_each_pair, 0);
465 rb_objc_define_method(rb_cHash, "each", nshash_each_pair, 0);
466 rb_objc_define_method(rb_cHash, "keys", nshash_keys, 0);
467 rb_objc_define_method(rb_cHash, "values", nshash_values, 0);
468 rb_objc_define_method(rb_cHash, "values_at", nshash_values_at, -1);
469 rb_objc_define_method(rb_cHash, "shift", nshash_shift, 0);
470 rb_objc_define_method(rb_cHash, "delete", nshash_delete, 1);
471 rb_objc_define_method(rb_cHash, "delete_if", nshash_delete_if, 0);
472 rb_objc_define_method(rb_cHash, "select", nshash_select, 0);
473 rb_objc_define_method(rb_cHash, "reject", nshash_reject, 0);
474 rb_objc_define_method(rb_cHash, "reject!", nshash_reject_bang, 0);
475 rb_objc_define_method(rb_cHash, "clear", nshash_clear, 0);
476 // XXX: #invert is a private method on NSMutableDictionary, so to not
477 // break things we do not implement it.
478 rb_objc_define_method(rb_cHash, "update", nshash_update, 1);
479 rb_objc_define_method(rb_cHash, "merge!", nshash_update, 1);
480 rb_objc_define_method(rb_cHash, "merge", nshash_merge, 1);
481 rb_objc_define_method(rb_cHash, "replace", nshash_replace, 1);
482 rb_objc_define_method(rb_cHash, "assoc", nshash_assoc, 1);
483 rb_objc_define_method(rb_cHash, "rassoc", nshash_rassoc, 1);
484 rb_objc_define_method(rb_cHash, "flatten", nshash_flatten, -1);
485 rb_objc_define_method(rb_cHash, "include?", nshash_has_key, 1);
486 rb_objc_define_method(rb_cHash, "member?", nshash_has_key, 1);
487 rb_objc_define_method(rb_cHash, "key?", nshash_has_key, 1);
488 rb_objc_define_method(rb_cHash, "has_key?", nshash_has_key, 1);
489 rb_objc_define_method(rb_cHash, "value?", nshash_has_value, 1);
490 rb_objc_define_method(rb_cHash, "has_value?", nshash_has_value, 1);
491 rb_objc_define_method(rb_cHash, "compare_by_identity",
492 nshash_compare_by_id, 0);
493 rb_objc_define_method(rb_cHash, "compare_by_identity?",
494 nshash_compare_by_id_p, 0);
495 }
496
497 // NSDictionary + NSMutableDictionary primitives. These are added automatically
498 // on singleton classes of pure NSDictionaries. Our implementation just calls
499 // the original methods, by tricking the receiver's class.
500
501 #define PREPARE_RCV(x) \
502 Class __old = *(Class *)x; \
503 *(Class *)x = (Class)rb_cCFHash;
504
505 #define RESTORE_RCV(x) \
506 *(Class *)x = __old;
507
508 static unsigned
509 nshash_count(id rcv, SEL sel)
510 {
511 PREPARE_RCV(rcv);
512 const unsigned count = [rcv count];
513 RESTORE_RCV(rcv);
514 return count;
515 }
516
517 static id
518 nshash_keyEnumerator(id rcv, SEL sel)
519 {
520 PREPARE_RCV(rcv);
521 id keys = [rcv allKeys];
522 RESTORE_RCV(rcv);
523 return [keys objectEnumerator];
524 }
525
526 static id
527 nshash_objectForKey(id rcv, SEL sel, id key)
528 {
529 PREPARE_RCV(rcv);
530 id value = [rcv objectForKey:key];
531 RESTORE_RCV(rcv);
532 return value;
533 }
534
535 static void
536 nshash_setObjectForKey(id rcv, SEL sel, id value, id key)
537 {
538 PREPARE_RCV(rcv);
539 [rcv setObject:value forKey:key];
540 RESTORE_RCV(rcv);
541 }
542
543 static void
544 nshash_getObjectsAndKeys(id rcv, SEL sel, id *objs, id *keys)
545 {
546 PREPARE_RCV(rcv);
547 [rcv getObjects:objs andKeys:keys];
548 RESTORE_RCV(rcv);
549 }
550
551 static void
552 nshash_removeObjectForKey(id rcv, SEL sel, id key)
553 {
554 PREPARE_RCV(rcv);
555 [rcv removeObjectForKey:key];
556 RESTORE_RCV(rcv);
557 }
558
559 static void
560 nshash_removeAllObjects(id rcv, SEL sel)
561 {
562 PREPARE_RCV(rcv);
563 [rcv removeAllObjects];
564 RESTORE_RCV(rcv);
565 }
566
567 static bool
568 nshash_isEqual(id rcv, SEL sel, id other)
569 {
570 PREPARE_RCV(rcv);
571 const bool res = [rcv isEqualToDictionary:other];
572 RESTORE_RCV(rcv);
573 return res;
574 }
575
576 static bool
577 nshash_containsObject(id rcv, SEL sel, id value)
578 {
579 PREPARE_RCV(rcv);
580 const bool res = [[rcv allKeysForObject:value] count] > 0;
581 RESTORE_RCV(rcv);
582 return res;
583 }
584
585 void
586 rb_objc_install_hash_primitives(Class klass)
587 {
588 rb_objc_install_method2(klass, "count", (IMP)nshash_count);
589 rb_objc_install_method2(klass, "keyEnumerator", (IMP)nshash_keyEnumerator);
590 rb_objc_install_method2(klass, "objectForKey:", (IMP)nshash_objectForKey);
591 rb_objc_install_method2(klass, "getObjects:andKeys:",
592 (IMP)nshash_getObjectsAndKeys);
593 rb_objc_install_method2(klass, "isEqual:", (IMP)nshash_isEqual);
594 rb_objc_install_method2(klass, "containsObject:",
595 (IMP)nshash_containsObject);
596
597 const bool mutable =
598 class_getSuperclass(klass) == (Class)rb_cNSMutableHash;
599
600 if (mutable) {
601 rb_objc_install_method2(klass, "setObject:forKey:",
602 (IMP)nshash_setObjectForKey);
603 rb_objc_install_method2(klass, "removeObjectForKey:",
604 (IMP)nshash_removeObjectForKey);
605 rb_objc_install_method2(klass, "removeAllObjects",
606 (IMP)nshash_removeAllObjects);
607 }
608
609 //rb_objc_define_method(*(VALUE *)klass, "alloc", hash_alloc, 0);
610 }
Something went wrong with that request. Please try again.