2011-06-14 55 views
8

我想出了这个问题,当我试图回答this。以下是预期的行为:Module.nesting内instance_eval/exec或module_eval/exec

module A 
    p Module.nesting 
end 
# => [A] 

但以下几点:

A.instance_eval{p Module.nesting} 
A.instance_exec{p Module.nesting} 
A.module_eval{p Module.nesting} 
A.module_exec{p Module.nesting} 

都返回[]。为什么这些不能像上面那样工作?

其他问题

穆太短提出一个有趣的问题。如果这是正确的,那么Module.nesting将是取决于文字上下文的方法和变量之一,如Method#source_location,__FILE__。这种理解是否正确?如果是这样,有人可以提供依赖于文字上下文的这些方法/变量的清单吗?我认为这对参考很有用。

+1

[“返回嵌套在调用点的模块列表”](http://ruby-doc.org/core/classes/Module.html#M000441),但是当您说时没有打开的“模块” 'A.module_eval'你只是在'A'的背景下行事。 'Module.nesting'似乎更多地是与Ruby运行时环境交谈的解析器。 – 2011-06-14 01:59:18

+0

@mu太短由你的评论启发,我加入到我的问题。 – sawa 2011-06-14 02:17:12

回答

7

警告:这有点漫长。由于文档有点简单,浏览Ruby源代码似乎是必要的。如果你不关心香肠是如何制作的,可以随意跳到最后。


1.9.2 Module.nestingeval.c实现这样的:

static VALUE 
rb_mod_nesting(void) 
{ 
    VALUE ary = rb_ary_new(); 
    const NODE *cref = rb_vm_cref(); 

    while (cref && cref->nd_next) { 
     VALUE klass = cref->nd_clss; 
     if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && 
      !NIL_P(klass)) { 
      rb_ary_push(ary, klass); 
     } 
     cref = cref->nd_next; 
    } 
    return ary; 
} 

我不知道Ruby的内部是很好,但我读了while循环是这样的:从提取链接的cref列出所有与班级相关的节点,但不是来自eval。该NODE_FL_CREF_PUSHED_BY_EVAL位仅设置在这里:

/* block eval under the class/module context */ 
static VALUE 
yield_under(VALUE under, VALUE self, VALUE values) 

多一点grepping和阅读表明instance_eval并最终通过yield_under去。我将离开检查instance_exec,module_evalmodule_exec作为读者的练习。在任何情况下,看起来instance_eval明确排除在Module.nesting列表中;然而,这比其他任何事都更让人分心,它只是意味着你不会看到提及的事物。

所以现在的问题是“什么是NODErb_vm_cref()?”。

如果您在node.h看,你会看到不同的Ruby关键词和语言结构一堆NODE常量:

  • NODE_BLOCK
  • NODE_BREAK
  • NODE_CLASS
  • NODE_MODULE
  • NODE_DSYM
  • ...

所以我猜想NODE是指令树中的一个节点。我

Module.nesting了很好的这行似乎更多有关评论谈话的解析器

猜想。但我们会继续前进。

rb_vm_cref函数只是vm_get_cref的包装,它是vm_get_cref0的包装。什么是vm_get_cref0?它是所有关于这一点:

static NODE * 
vm_get_cref0(const rb_iseq_t *iseq, const VALUE *lfp, const VALUE *dfp) 
{ 
    while (1) { 
     if (lfp == dfp) { 
      return iseq->cref_stack; 
     } 
     else if (dfp[-1] != Qnil) { 
      return (NODE *)dfp[-1]; 
     } 
     dfp = GET_PREV_DFP(dfp); 
    } 
} 

所有这三个函数的自变量来直出这种控制框架:

rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp); 

iseq似乎是一个指令序列和lfpdfp是帧指针:

VALUE *lfp;     // cfp[6], local frame pointer 
VALUE *dfp;     // cfp[7], dynamic frame pointer 

cref_stack定义是相关的:

/* klass/module nest information stack (cref) */ 
NODE *cref_stack; 

因此,看起来您正在从rb_vm_cref中获得某种呼叫或嵌套堆栈。


现在回到手头的细节。当你这样做:

module A 
    p Module.nesting 
end 

你必须module Acref链表(被过滤以产生Module.nesting结果数组)你有没有打end呢。当你说的这些:

A.instance_eval { puts Module.nesting } 
A.instance_exec { puts Module.nesting } 
A.module_eval { puts Module.nesting } 
A.module_exec { puts Module.nesting } 

你不会有crefmodule A了,因为你已经打了end弹出module A堆栈。但是,如果你这样做:

module A 
    instance_eval { puts Module.nesting.inspect } 
    instance_exec { puts Module.nesting.inspect } 
    module_eval { puts Module.nesting.inspect } 
    module_exec { puts Module.nesting.inspect } 
end 

你会看到这样的输出:

[A] 
[A] 
[A] 
[A] 

因为module A尚未关闭(和弹出cref)呢。

要玩完的Module.nesting documentation这样说:

返回嵌套在呼叫点的模块列表。

我认为这句话结合内部审查表明,Module.nesting确实取决于它被调用的特定文字上下文。

如果任何在Ruby内部有更多经验的人都可以添加任何东西,我可以把它作为社区维基交给SO社区。


UPDATE:所有这一切都适用于class_eval以及它对module_eval,它也适用于1.9.3以及它对1.9.2。

+0

感谢您的印象深刻和详细的解释。维基可能是一个好主意。 – sawa 2011-06-14 05:10:20