Module.nesting внутри instance_eval / exec или module_eval / exec - PullRequest
8 голосов
/ 14 июня 2011

Я задал этот вопрос, когда пытался ответить на это .Следующее является ожидаемым поведением:

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 Ответ

7 голосов
/ 14 июня 2011

Предупреждение : Это немного длинная болтовня.Небольшая экскурсия по исходному коду 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...