Предупреждение : Это немного длинная болтовня.Небольшая экскурсия по исходному коду Ruby кажется необходимой, поскольку документация немного тонкая.Не стесняйтесь переходить до конца, если вам не важно, как производится колбаса.
1.9.2 Module.nesting
реализован в eval.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)
Немного больше подсказок и чтения показывает, что instance_eval
в конечном итоге проходит через yield_under
.Я оставлю проверки instance_exec
, module_eval
и module_exec
в качестве упражнений для читателя.В любом случае, похоже, что instance_eval
явно исключен из списка Module.nesting
;это, однако, больше отвлекает, чем что-либо еще, это просто означает, что вы не увидите то, о чем упоминали уловки.
Итак, теперь вопрос в том, "что такое NODE
и rb_vm_cref()
? ".
Если вы заглянете в node.h
, вы увидите набор констант NODE для различных ключевых слов и языковых структур Ruby:
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
представляется последовательностью команд, аlfp
и dfp
- это указатели фреймов:
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 A
в связанном списке cref
(который фильтруется для получения массива результата 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 }
У вас больше не будет module A
в cref
, потому что вы уже ударили end
popped 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
гласит:
Возвращает список модулей, вложенных в точке вызова.
Я думаю, что это утверждение в сочетании с обзором внутренних органов указывает на то, что Module.nesting
на самом деле зависит от конкретного буквального контекста, в котором оно называется.
Если кто-то с большим опытом вRuby внутренности есть что добавить, я могу передать это сообществу SO как вики сообщества.
ОБНОВЛЕНИЕ : Все это относится к class_eval
так же, как и кк module_eval
, и это также относится к 1.9.3, как и к 1.9.2.