module.nesting 在instance_eval/exec 或 module_eval/exec 中

发布于 2024-11-15 01:29:43 字数 555 浏览 0 评论 0原文

当我试图回答这个问题时,我想到了这个问题。以下是预期行为:

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}

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

附加问题

Mu太短了,提出了一个有趣的观点。如果这是正确的,那么 Module.nesting 将是依赖于文字上下文的方法和变量之一,例如 Method#source_location__FILE__。这种理解正确吗?如果是这样,有人可以提供这些依赖于文字上下文的方法/变量的清单吗?我觉得还是很有参考价值的。

I came up with this question when I was trying to answer this. The following is an expected behaviour:

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

But the following:

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

all return []. Why do these not work as the above?

Additional Question

Mu is too short suggested an interesting point. If that is correct, then Module.nesting would be one of the methods and variables that are dependent on the literal context like Method#source_location, __FILE__. Is this understanding correct? If so, can someone provide the inventory of these methods/variables that are dependent on the literal context? I think it would be useful for reference.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

囚你心 2024-11-22 01:29:43

警告:这有点长而且漫无目的。由于文档有点单薄,因此似乎有必要浏览一下 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)

更多的 grep 和阅读表明 instance_eval 最终会经历 yield_under。我将检查 instance_execmodule_evalmodule_exec 作为读者的练习。无论如何,instance_eval 似乎已从 Module.nesting 列表中明确排除;然而,这比其他任何事情都更让人分心,它只是意味着您不会看到评估提到的内容。

所以现在的问题是“NODE 和 rb_vm_cref() 到底是什么?”。

如果您查看 node.h,您会看到一堆针对各种 Ruby 关键字和语言结构的 NODE 常量:

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

所以我猜测 NODE 是指令树中的一个节点。这很符合我的

Module.nesting 似乎更多的是与解析器对话

Module.nesting 似乎更多的是在评论中 猜想。但无论如何我们都会继续前进。

rb_vm_cref 函数只是 vm_get_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

您将在 cref 链接列表中拥有 module A(经过过滤以生成 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 }

您将不再有 cref 中的 module 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]

因为模块 A 尚未关闭(并从 cref 弹出)。

最后,Module.nesting 文档 说的是:

返回调用点嵌套的模块列表。

我认为这个陈述与内部审查相结合表明 Module.nesting 实际上依赖于调用它的特定文字上下文。

如果任何在 Ruby 内部有更多经验的人有任何需要添加的内容,我可以将其作为社区 wiki 交给 SO 社区。


更新:所有这些都适用于class_eval以及module_eval,它也适用于1.9.3以及它1.9.2.

Warning: This is a little long and rambling. A bit of a tour through the Ruby source code seems necessary as the documentation is a bit thin. Feel free to skip to the end if you don't care about how sausage is made.


The 1.9.2 Module.nesting is implemented in eval.c like this:

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;
}

I don't know the Ruby internals that well but I read the while loop like this: extract from the cref linked list all the nodes that are associated with a class-like thing but didn't come from eval. The NODE_FL_CREF_PUSHED_BY_EVAL bit is only set in here:

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

A bit more grepping and reading reveals that instance_eval does end up going through yield_under. I'll leave checking instance_exec, module_eval, and module_exec as exercises for the reader. In any case, it looks like instance_eval is explicitly excluded from the Module.nesting list; this is, however, more of a distraction than anything else, it just means that you won't see something the evals mentioned.

So now the question is "what are NODE and rb_vm_cref() all about?".

If you look in node.h you'll see a bunch of NODE constants for the various Ruby keywords and language structures:

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

so I'd guess that NODE is a node in the instruction tree. This lines up nicely with my

Module.nesting seems to be more about talking to the parser

conjecture in the comment. But we'll keep going anyway.

The rb_vm_cref function is just a wrapper for vm_get_cref which is a wrapper for vm_get_cref0. What is vm_get_cref0 all about? It is all about this:

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);
    }
}

All three arguments to the function come straight out of this control frame:

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

The iseq appears to be an instruction sequence and the lfp and dfp are frame pointers:

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

The definition of cref_stack is relevant:

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

So it looks like you're getting some sort of call or nesting stack out of rb_vm_cref.


Now back to the specifics at hand. When you do this:

module A
  p Module.nesting
end

You'll have module A in the cref linked list (which is filtered to produce the Module.nesting result array) as you haven't hit the end yet. When you say these:

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

You won't have module A in cref anymore because you've already hit the end popped module A off the stack. However, if you do this:

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

You'll see this output:

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

because the module A hasn't been closed (and popped off cref) yet.

To finish off, the Module.nesting documentation says this:

Returns the list of Modules nested at the point of call.

I think this statement combined with the review of the internals indicates that Module.nesting does in fact depend on the specific literal context in which it is called.

If anyone with more experience in the Ruby internals has anything to add I can hand this over to the SO community as a community wiki.


UPDATE: All of this applies to class_eval as well as it does to module_eval and it also applies to 1.9.3 as well as it does to 1.9.2.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文