From a0c466023b36caf62c49eae3b4f405b1ed15e7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lourens=20Naud=C3=A9?= Date: Fri, 15 May 2015 23:20:48 -0400 Subject: [PATCH] Implement Symbol.each to expose the Symbol table as an Enumerator --- ChangeLog | 17 ++++++++++++ include/ruby/intern.h | 4 ++- string.c | 2 ++ symbol.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ test/ruby/test_parse.rb | 6 ---- test/ruby/test_symbol.rb | 16 +++++++++++ 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8b12c47..bf5ec00 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +Thu May 21 10:44:07 2015 Lourens Naudé + + * symbol.c (rb_sym_each): Implement `rb_sym_each` to expose the Symbol table + as an Enumerator. This is a more versatile method to walking the Symbol table + with the slower `rb_sym_all_symbols` API. Also possible to count total Symbols + with `Symbol.each.size` + + * include/ruby/intern.h (rb_sym_each): Expose `rb_sym_each` API. Move + `rb_sym_all_symbols` to a symbol.c specific section (does not live in parse.y + anymore) + + * string.c (Init_string): Implement Symbol.each, extend Symbol with Enumerable + + * test/ruby/test_symbol.rb: Test for Symbol API addition. + + * test/ruby/test_parse.rb: Moved `test_all_symbols` to test/ruby/test_symbol.rb + Thu May 21 04:11:03 2015 Koichi Sasada * iseq.c (exception_type2symbol): show correct bug message. diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 1d4d90a..81ea1fc 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -636,7 +636,6 @@ VALUE rb_backref_get(void); void rb_backref_set(VALUE); VALUE rb_lastline_get(void); void rb_lastline_set(VALUE); -VALUE rb_sym_all_symbols(void); /* process.c */ void rb_last_status_set(int status, rb_pid_t pid); VALUE rb_last_status_get(void); @@ -790,6 +789,9 @@ long rb_str_offset(VALUE, long); size_t rb_str_capacity(VALUE); VALUE rb_str_ellipsize(VALUE, long); VALUE rb_str_scrub(VALUE, VALUE); +/* symbol.c */ +VALUE rb_sym_all_symbols(void); +VALUE rb_sym_each(void); #if defined(__GNUC__) && !defined(__PCC__) #define rb_str_new(str, len) __extension__ ( \ diff --git a/string.c b/string.c index ca08540..c2e11d2 100644 --- a/string.c +++ b/string.c @@ -9234,9 +9234,11 @@ Init_String(void) rb_cSymbol = rb_define_class("Symbol", rb_cObject); rb_include_module(rb_cSymbol, rb_mComparable); + rb_include_module(rb_singleton_class(rb_cSymbol), rb_mEnumerable); rb_undef_alloc_func(rb_cSymbol); rb_undef_method(CLASS_OF(rb_cSymbol), "new"); rb_define_singleton_method(rb_cSymbol, "all_symbols", rb_sym_all_symbols, 0); /* in symbol.c */ + rb_define_singleton_method(rb_cSymbol, "each", rb_sym_each, 0); /* in symbol.c */ rb_define_method(rb_cSymbol, "==", sym_equal, 1); rb_define_method(rb_cSymbol, "===", sym_equal, 1); diff --git a/symbol.c b/symbol.c index 63064d3..8a00819 100644 --- a/symbol.c +++ b/symbol.c @@ -847,6 +847,77 @@ rb_sym_all_symbols(void) return ary; } +static int +symbols_each_i(st_data_t key, st_data_t value, st_data_t arg) +{ + VALUE sym = (VALUE)value; + + if (STATIC_SYM_P(sym)) { + rb_yield(sym); + return ST_CONTINUE; + } + else if (!DYNAMIC_SYM_P(sym)) { + rb_bug("invalid symbol: %s", RSTRING_PTR((VALUE)key)); + } + else if (!SYMBOL_PINNED_P(sym) && rb_objspace_garbage_object_p(sym)) { + RSYMBOL(sym)->fstr = 0; + return ST_DELETE; + } + else { + rb_yield(sym); + return ST_CONTINUE; + } + +} + +static VALUE +symbol_enum_size(VALUE hash, VALUE args, VALUE eobj) +{ +#if SIZEOF_LONG == SIZEOF_VOIDP + return ULONG2NUM(global_symbols.str_sym->num_entries); +#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP + return ULL2NUM(global_symbols.str_sym->num_entries); +#endif +} + +/* + * call-seq: + * Symobl.each {| sym | block } -> Symbol + * Symbol.each -> an_enumerator + * + * Calls block once for each symbol in the symbol table, passing the + * symbol as a parameter. + * + * If no block is given, an enumerator is returned instead. + * + * bacon = :bacon + * lettuce = :lettuce + * Symbol.each {|sym| puts sym } + * + * produces: + * + * ... many other symbols ... + * bacon + * lettuce + * + * It's also a cheaper alternative to Symbol.all_symbols.size to get the + * size of Ruby's symbol table. + * + * Symbol.count + * + * produces: + * + * 903 + */ + +VALUE +rb_sym_each(void) +{ + RETURN_SIZED_ENUMERATOR(Qnil, 0, 0, symbol_enum_size); + st_foreach(global_symbols.str_sym, symbols_each_i, 0); + return rb_cSymbol; +} + int rb_is_const_id(ID id) { diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 216cdc0..9f91125 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -846,12 +846,6 @@ def test_intern assert_equal(':"foo=="', "foo==".intern.inspect) end - def test_all_symbols - x = Symbol.all_symbols - assert_kind_of(Array, x) - assert_empty(x.reject {|s| s.is_a?(Symbol) }) - end - def test_is_class_id c = Class.new assert_raise(NameError) do diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb index 0a055db..9776ae2 100644 --- a/test/ruby/test_symbol.rb +++ b/test/ruby/test_symbol.rb @@ -12,6 +12,22 @@ def assert_eval_inspected(sym, valid = true) assert_nothing_raised(SyntaxError) {assert_equal(sym, eval(n))} end + def test_all_symbols + x = Symbol.all_symbols + assert_kind_of(Array, x) + assert_empty(x.reject {|s| s.is_a?(Symbol) }) + end + + def test_each + x = Symbol.each.size + assert_kind_of(Fixnum, x) + assert_equal x, Symbol.all_symbols.size + assert_equal x, Symbol.count + assert_equal Symbol.to_a, Symbol.all_symbols + answer_to_life = :bacon_lettuce_tomato + assert_equal [:bacon_lettuce_tomato], Symbol.grep(/bacon_lettuce_tomato/) + end + def test_inspect_invalid # 2) Symbol#inspect sometimes returns invalid symbol representations: assert_eval_inspected(:"!")