[ruby-dev:28735] Module#toplevel_eval (Re: binding の仕様変更? or バグ?)
From:
Hidetoshi NAGAI <nagai@...>
Date:
2006-06-14 23:35:03 UTC
List:
ruby-dev #28735
永井@知能.九工大です.
# 変なことばかりやってるんで全く相手にされてないですね...(;_;)
他に悪影響がでないということを条件に,とりあえず実装してみました.
現状に対して非常に困っておりますし,互換性にも影響がないはず
(だと思います) なので,なんとか採用していただくことはできないでしょうか.
CVS の ruby_1_8 との差分を添付します.
Module#toplevel_eval は module_eval と同様に文字列またはブロックで
コード片を受け取り,評価します.
ただし,評価されるコード片の中では,
* モジュールで定義されたメソッドを関数型メソッドとして呼び出せる.
* ::CONST のような定数アクセスは,そのモジュールのコンテキストを
最優先に行う.見つからなければ本来のトップレベルを検索するが,
代入の場合はそのモジュール (の特異クラスの) のコンテキストで行われる.
というようになります.
これにより,関数型のメソッドを使って作られたスクリプトを,
モジュールをトップレベルとして load しても動くはずです.
また,::CONST = val の実行などで本来のトップレベルが汚染されることも
避けられると思います.
なお,評価されるコードの中で新たにスレッドを作った場合,
設定はそのスレッドに引き継がれます.
ただし評価されるコード中であっても,requrie (autoload 含む) が
呼ばれた場合には,require が完了するまでこの性質は無効にされます.
# ライブラリでのトラブルを避けるためです.
ちょっと長いですが,module_eval との違いは次のようになります.
======================================================
module M
HOGE = 111
def foo(*args)
p [self, args]
end
end
class X
end
########################################
begin
p '-----(1)-----'
M.module_eval{foo(1,2,3)}
rescue
p $!
end
#=> #<NoMethodError: undefined method `foo' for M:Module>
begin
p '-----(2)-----'
M.toplevel_eval{foo(1,2,3)}
rescue
p $!
end
#=> [M, [1, 2, 3]]
begin
p '-----(3)-----'
M.module_eval{X.new.instance_eval{foo(1,2,3)}}
rescue
p $!
end
#=> #<NoMethodError: undefined method `foo' for #<X:0x401d1308>>
begin
p '-----(4)-----'
M.toplevel_eval{X.new.instance_eval{foo(1,2,3)}}
rescue
p $!
end
#=> [#<X:0x401d1178>, [1, 2, 3]]
begin
p '-----(5)-----'
M.module_eval{Object.new.instance_eval{foo(1,2,3)}}
rescue
p $!
end
#=> #<NoMethodError: undefined method `foo' for #<Object:0x401d1074>>
begin
p '-----(6)-----'
M.toplevel_eval{Object.new.instance_eval{foo(1,2,3)}}
rescue
p $!
end
#=> [#<Object:0x401d0ed0>, [1, 2, 3]]
begin
p '-----(9)-----'
M.module_eval{Object.new.instance_eval{p HOGE}}
rescue
p $!
end
#=> #<NameError: uninitialized constant HOGE>
begin
p '-----(10)-----'
M.toplevel_eval{Object.new.instance_eval{p HOGE}}
rescue
p $!
end
#=> 111
begin
p '-----(11)-----'
M.module_eval{X.new.instance_eval{p HOGE}}
rescue
p $!
end
#=> #<NameError: uninitialized constant HOGE>
begin
p '-----(12)-----'
M.toplevel_eval{X.new.instance_eval{p HOGE}}
rescue
p $!
end
#=> 111
begin
p '-----(13)-----'
M.module_eval{p ::HOGE}
rescue
p $!
end
#=> #<NameError: uninitialized constant HOGE>
begin
p '-----(14)-----'
M.toplevel_eval{p ::HOGE}
rescue
p $!
end
#=> 111
begin
p '-----(15)-----'
M.module_eval{p ::M}
rescue
p $!
end
#=> M
begin
p '-----(16)-----'
M.toplevel_eval{p ::M}
rescue
p $!
end
#=> M
begin
p '-----(17)-----'
M.module_eval{Object.new.instance_eval{p ::HOGE}}
rescue
p $!
end
#=> #<NameError: uninitialized constant HOGE>
begin
p '-----(18)-----'
M.toplevel_eval{Object.new.instance_eval{p ::HOGE}}
rescue
p $!
end
#=> 111
begin
p '-----(19)-----'
M.module_eval{X.new.instance_eval{p ::HOGE}}
rescue
p $!
end
#=> #<NameError: uninitialized constant HOGE>
begin
p '-----(20)-----'
M.toplevel_eval{X.new.instance_eval{p ::HOGE}}
rescue
p $!
end
#=> 111
begin
p '-----(21)-----'
M.module_eval{
def hoge(*args)
p [self, args]
end
hoge(:a, :b, :c)
Object.new.instance_eval{hoge(:d, :e, :f)}
}
rescue
p $!
end
#=> #<NoMethodError: undefined method `hoge' for M:Module>
begin
p '-----(22)-----'
M.toplevel_eval{
def fuga(*args)
p [self, args]
end
fuga(:a, :b, :c)
Object.new.instance_eval{fuga(:d, :e, :f)}
}
rescue
p $!
end
#=> [M, [:a, :b, :c]]
#=> [#<Object:0x401d04bc>, [:d, :e, :f]]
p '--------------------------'
p m = Module.new #=> #<Module:0x401d0480>
m.toplevel_eval{
FOO = :foo
::BAR = :bar
def baz(*args)
[self, args]
end
p baz(1, FOO, ::FOO, ::BAR)
#=> [#<Module:0x401d0480>, [1, :foo, :foo, :bar]]
p Object.new.instance_eval{baz(2, FOO, ::FOO, ::BAR)}
#=> [#<Object:0x401d02dc>, [2, :foo, :foo, :bar]]
}
p m.constants #=> ["BAR", "FOO"]
======================================================
--
永井 秀利 (九工大 知能情報)
nagai@ai.kyutech.ac.jp
Attachments (1)
eval.c.toplevel_eval-diff
(10.1 KB, text/x-diff)
Index: eval.c
===================================================================
RCS file: /var/cvs/src/ruby/eval.c,v
retrieving revision 1.616.2.173
diff -u -r1.616.2.173 eval.c
--- eval.c 7 Jun 2006 00:19:11 -0000 1.616.2.173
+++ eval.c 14 Jun 2006 22:46:15 -0000
@@ -469,17 +469,39 @@
rb_add_method(CLASS_OF(klass), ID_ALLOCATOR, 0, NOEX_UNDEF);
}
+static void current_toplevel_set(VALUE);
+static VALUE current_toplevel_get(void);
+
static NODE*
search_method(klass, id, origin)
VALUE klass, *origin;
ID id;
{
NODE *body;
+ VALUE top;
if (!klass) return 0;
+
+ if (RCLASS(klass)->super == rb_cObject && (top = current_toplevel_get())) {
+ /* search current toplevel module before Object class */
+ if (st_lookup(RCLASS(top)->m_tbl, id, (st_data_t *)&body)) {
+ if (origin) *origin = top;
+ return body;
+ }
+ }
+
while (!st_lookup(RCLASS(klass)->m_tbl, id, (st_data_t *)&body)) {
klass = RCLASS(klass)->super;
if (!klass) return 0;
+
+ if (RCLASS(klass)->super == rb_cObject
+ && (top = current_toplevel_get())) {
+ /* search current toplevel module before Object class */
+ if (st_lookup(RCLASS(top)->m_tbl, id, (st_data_t *)&body)) {
+ klass = top;
+ break;
+ }
+ }
}
if (origin) *origin = klass;
@@ -1849,6 +1871,24 @@
#define ruby_cbase (ruby_cref->nd_clss)
static VALUE
+ev_const_defined_at_top(id)
+ ID id;
+{
+ VALUE result;
+ VALUE top = current_toplevel_get();
+
+ if (!top) {
+ return Qfalse;
+ }
+ if (RCLASS(top)->iv_tbl && st_lookup(RCLASS(top)->iv_tbl, id, &result)) {
+ if (result != Qundef) {
+ return Qtrue;
+ }
+ }
+ return Qfalse;
+}
+
+static VALUE
ev_const_defined(cref, id, self)
NODE *cref;
ID id;
@@ -1857,6 +1897,11 @@
NODE *cbase = cref;
VALUE result;
+ /* if toplevel module is defined, search current toplevel first */
+ if (RTEST(ev_const_defined_at_top(id))) {
+ return Qtrue;
+ }
+
while (cbase && cbase->nd_next) {
struct RClass *klass = RCLASS(cbase->nd_clss);
@@ -1881,6 +1926,16 @@
{
NODE *cbase = cref;
VALUE result;
+ VALUE top = current_toplevel_get();
+
+ if (top) {
+ /* if toplevel module is defined, search current toplevel first */
+ if (RCLASS(top)->iv_tbl && st_lookup(RCLASS(top)->iv_tbl, id, &result)) {
+ if (result != Qundef) {
+ return result;
+ }
+ }
+ }
while (cbase && cbase->nd_next) {
VALUE klass = cbase->nd_clss;
@@ -2458,9 +2513,14 @@
break;
case NODE_COLON3:
- if (rb_const_defined_from(rb_cObject, node->nd_mid)) {
- return "constant";
- }
+ {
+ if (RTEST(ev_const_defined_at_top(node->nd_mid))) {
+ return "constant";
+ }
+ if (rb_const_defined_from(rb_cObject, node->nd_mid)) {
+ return "constant";
+ }
+ }
break;
case NODE_NTH_REF:
@@ -2844,13 +2904,19 @@
return c;
}
else if (nd_type(cpath) == NODE_COLON2) {
- return ruby_cbase;
+ return ruby_cbase;
}
else if (ruby_wrapper) {
return ruby_wrapper;
}
else {
- return rb_cObject;
+ VALUE top = current_toplevel_get();
+ if (top) {
+ return top;
+ }
+ else {
+ return rb_cObject;
+ }
}
}
@@ -3643,7 +3709,13 @@
rb_const_set(class_prefix(self, node->nd_else), node->nd_else->nd_mid, result);
}
else {
- rb_const_set(ruby_cbase, node->nd_vid, result);
+ VALUE top = current_toplevel_get();
+ if (top) {
+ rb_const_set(top, node->nd_vid, result);
+ }
+ else {
+ rb_const_set(ruby_cbase, node->nd_vid, result);
+ }
}
break;
@@ -3723,7 +3795,14 @@
break;
case NODE_COLON3:
- result = rb_const_get_from(rb_cObject, node->nd_mid);
+ {
+ if (RTEST(ev_const_defined_at_top(node->nd_mid))) {
+ result = rb_const_get_from(current_toplevel_get(), node->nd_mid);
+ }
+ else {
+ result = rb_const_get_from(rb_cObject, node->nd_mid);
+ }
+ }
break;
case NODE_NTH_REF:
@@ -5187,7 +5266,13 @@
rb_const_set(class_prefix(self, lhs->nd_else), lhs->nd_else->nd_mid, val);
}
else {
- rb_const_set(ruby_cbase, lhs->nd_vid, val);
+ VALUE top = current_toplevel_get();
+ if (top) {
+ rb_const_set(top, lhs->nd_vid, val);
+ }
+ else {
+ rb_const_set(ruby_cbase, lhs->nd_vid, val);
+ }
}
break;
@@ -6718,6 +6803,90 @@
return specific_eval(argc, argv, mod, mod);
}
+
+struct mod_toplevel_eval_arg {
+ int argc;
+ VALUE *argv;
+ VALUE mod;
+ VALUE orig_top;
+};
+
+VALUE
+mod_toplevel_eval_call(arg)
+ struct mod_toplevel_eval_arg *arg;
+{
+ current_toplevel_set(arg->mod);
+ return specific_eval(arg->argc, arg->argv, arg->mod, arg->mod);
+}
+
+VALUE
+mod_toplevel_eval_ensure(arg)
+ struct mod_toplevel_eval_arg *arg;
+{
+ RCLASS(arg->mod)->iv_tbl = 0;
+ RCLASS(arg->mod)->m_tbl = 0;
+
+ current_toplevel_set(arg->orig_top);
+}
+
+/*
+ * call-seq:
+ * mod.toplevel_eval(string [, filename [, lineno]] ) => obj
+ * mod.toplevel_eval{| | block } => obj
+ *
+ * Evaluates the string or block in the context of _mod_, as if
+ * the context is the toplevel context. <code>toplevel_eval</code>
+ * returns the result of evaluating its argument.
+ * In evaluating the argument, methods defined in _mod_ are regarded
+ * as toplevel functions. Constants in _mod_ are valid in evaluating.
+ * And when access <code>::CONST</code>, search in _mod_ first.
+ * Even if at calling this method, the setting is ignored during
+ * <code>require file</code> (or autoload file) to prevent troubles
+ * on libraries.
+ * Threads derived from the code inherit the context.
+ *
+ * p m = Module.new #=> #<Module:0x401d0480>
+ * m.toplevel_eval{
+ * FOO = :foo
+ * ::BAR = :bar
+ * def baz(*args)
+ * [self, args]
+ * end
+ * p baz(1, FOO, ::FOO, ::BAR)
+ * #=> [#<Module:0x401d0480>, [1, :foo, :foo, :bar]]
+ * p Object.new.instance_eval{baz(2, FOO, ::BAR)}
+ * #=> [#<Object:0x401d02dc>, [2, :foo, :foo, :bar]]
+ * }
+ * p m.constants #=> ["BAR", "FOO"]
+ *
+ */
+VALUE
+rb_mod_toplevel_eval(argc, argv, mod)
+ int argc;
+ VALUE *argv;
+ VALUE mod;
+{
+ volatile VALUE curr_top = current_toplevel_get();
+ struct mod_toplevel_eval_arg arg;
+
+ NEWOBJ(klass, struct RClass);
+ /* OBJSETUP(klass, RBASIC(mod)->klass, T_ICLASS);*/
+ OBJSETUP(klass, rb_cClass, T_ICLASS);
+
+ RCLASS(klass)->super = mod;
+ RCLASS(klass)->iv_tbl = RCLASS(mod)->iv_tbl;
+ RCLASS(klass)->m_tbl = RCLASS(mod)->m_tbl;
+
+ arg.argc = argc;
+ arg.argv = argv;
+ arg.mod = (VALUE)klass;
+ arg.orig_top = curr_top;
+
+ rb_ensure(mod_toplevel_eval_call, (VALUE)&arg,
+ mod_toplevel_eval_ensure, (VALUE)&arg);
+}
+
+
VALUE rb_load_path;
NORETURN(static void load_failed _((VALUE)));
@@ -7075,6 +7244,7 @@
{
VALUE result = Qnil;
volatile VALUE errinfo = ruby_errinfo;
+ volatile VALUE orig_top = current_toplevel_get();
int state;
struct {
NODE *node;
@@ -7092,6 +7262,7 @@
saved.node = ruby_current_node;
saved.func = ruby_frame->last_func;
saved.safe = ruby_safe_level;
+ current_toplevel_set((VALUE)0); /* ignore current_toplevel at require */
PUSH_TAG(PROT_NONE);
if ((state = EXEC_TAG()) == 0) {
VALUE feature, path;
@@ -7134,6 +7305,7 @@
}
}
POP_TAG();
+ current_toplevel_set(orig_top);
ruby_current_node = saved.node;
ruby_set_current_source();
ruby_frame->last_func = saved.func;
@@ -7904,6 +8076,8 @@
rb_define_method(rb_cModule, "module_eval", rb_mod_module_eval, -1);
rb_define_method(rb_cModule, "class_eval", rb_mod_module_eval, -1);
+ rb_define_method(rb_cModule, "toplevel_eval", rb_mod_toplevel_eval, -1);
+
rb_undef_method(rb_cClass, "module_function");
rb_define_private_method(rb_cModule, "remove_method", rb_mod_remove_method, -1);
@@ -9763,6 +9937,7 @@
struct BLOCK *block;
struct iter *iter;
struct tag *tag;
+ VALUE toplevel;
VALUE klass;
VALUE wrapper;
NODE *cref;
@@ -9798,6 +9973,20 @@
VALUE thread;
};
+static void
+current_toplevel_set(top)
+ VALUE top;
+{
+ curr_thread->toplevel = top;
+}
+
+static VALUE
+current_toplevel_get(void)
+{
+ return curr_thread->toplevel;
+}
+
+
#define THREAD_RAISED 0x200 /* temporary flag */
#define THREAD_TERMINATING 0x400 /* persistent flag */
#define THREAD_FLAGS_MASK 0x400 /* mask for persistent flags */
@@ -9987,6 +10176,7 @@
rb_gc_mark(th->klass);
rb_gc_mark(th->wrapper);
+ rb_gc_mark(th->toplevel);
rb_gc_mark((VALUE)th->cref);
rb_gc_mark((VALUE)th->scope);
@@ -10343,6 +10533,7 @@
rb_thread_die(th)
rb_thread_t th;
{
+ th->toplevel = 0;
th->thgroup = 0;
th->status = THREAD_KILLED;
if (th->stk_ptr) free(th->stk_ptr);
@@ -11566,6 +11757,7 @@
th->scope = 0;\
th->klass = 0;\
th->wrapper = 0;\
+ th->toplevel = 0;\
th->cref = ruby_cref;\
th->dyna_vars = ruby_dyna_vars;\
th->block = 0;\
@@ -11744,6 +11936,7 @@
curr_thread->next->prev = th;
th->next = curr_thread->next;
curr_thread->next = th;
+ th->toplevel = curr_thread->toplevel;
th->priority = curr_thread->priority;
th->thgroup = curr_thread->thgroup;
}
@@ -12079,6 +12272,7 @@
if (th->status != THREAD_KILLED) {
rb_thread_ready(th);
if (th != main_thread) {
+ th->toplevel = 0;
th->thgroup = 0;
th->priority = 0;
th->status = THREAD_TO_KILL;
@@ -12554,6 +12748,7 @@
scope_dup(tag->scope);
}
th->thread = curr_thread->thread;
+ th->toplevel = curr_thread->toplevel;
th->thgroup = cont_protect;
for (vars = ruby_dyna_vars; vars; vars = vars->next) {