Когда анализируется строка Ruby? оценивать? казнят? - PullRequest
1 голос
/ 03 июля 2019

Я был немного удивлен, обнаружив, что person определяется следующей строкой кода, даже когда params[:person_id] не существует:

person = Person.find(params[:person_id]) if params[:person_id]

Я вроде ожидал, что Ruby сначалапроверьте оператор if и определите только person.На практике кажется, что person определено раньше, но остается nil.

. В ходе исследования я попробовал следующее:

irb> foo
# NameError (undefined local variable or method `foo' for main:Object)
irb> if false
irb>   foo = 'bar'
irb> end
irb> foo
# => nil

Первоначально foo не определено.Но затем он определяется, даже если на него ссылаются только в блоке if, который не оценивается.

Теперь я предполагаю, что вся программа анализируется (?) И что узел fooдобавляется в абстрактное синтаксическое дерево (то есть определяется).Затем программа выполняется (?), Но эта конкретная строка пропускается (не оценивается (?)), Поэтому foo равно nil (определено, но не установлено значение).

Я не уверенкак подтвердить или опровергнуть эту догадку.Как можно научиться копаться во внутренностях Ruby и выяснить, что происходит в этом конкретном сценарии?

1 Ответ

0 голосов
/ 05 июля 2019

Отвечая на мой собственный вопрос, Jay 's отвечает на аналогичный вопрос , связанный с разделом документов , где это объясняется:

Локальная переменная создается, когда синтаксический анализатор встречает назначение, а не когда назначение происходит

Более подробный анализ этого в Ruby Hacking Guide (нет ссылок на разделы, ищите или прокрутите к разделу «Определения локальной переменной»):

Кстати, он определяется, когда «появляется», это означает, что он определен, даже если он не былназначены.Начальное значение определенной [но еще не назначенной] переменной равно nil.

Это отвечает на первоначальный вопрос, но не позволяет узнать больше.


Jay и simonwo оба предложили Рубин под микроскопом Пэта Шонесси, который я очень хочу прочитать.

Кроме того, остальная часть Руководства по взлому Ruby охватывает много деталей и фактически рассматриваетбазовый C-код.Главы Objects и Parser были особенно актуальны для исходного вопроса о присвоении переменных (не столько в главе Variables и constants , она просто возвращает вас обратно кГлава об объектах).

Я также обнаружил, что полезным инструментом для просмотра работы синтаксического анализатора является Parser gem .Как только он установлен (gem install parser), вы можете начать изучать различные фрагменты кода, чтобы увидеть, что с ними делает анализатор.

Этот гем также включает утилиту ruby-parse, которая позволяет вам изучить способ Ruby.разбирает разные фрагменты кода.Опции -E и -L наиболее интересны для нас, а опция -e необходима, если мы просто хотим обработать фрагмент Ruby, такой как foo = 'bar'.Например:

> ruby-parse -E -e "foo = 'bar'"
foo = 'bar'   
^~~ tIDENTIFIER "foo"                           expr_cmdarg  [0 <= cond] [0 <= cmdarg] 
foo = 'bar'   
    ^ tEQL "="                                  expr_beg     [0 <= cond] [0 <= cmdarg] 
foo = 'bar'   
      ^~~~~ tSTRING "bar"                       expr_end     [0 <= cond] [0 <= cmdarg] 
foo = 'bar'   
           ^ false "$eof"                       expr_end     [0 <= cond] [0 <= cmdarg] 
(lvasgn :foo
  (str "bar"))
ruby-parse -L -e "foo = 'bar'"
s(:lvasgn, :foo,
  s(:str, "bar"))
foo = 'bar'
~~~ name      
    ~ operator        
~~~~~~~~~~~ expression
s(:str, "bar")
foo = 'bar'
          ~ end
      ~ begin         
      ~~~~~ expression

Обе ссылки, связанные сверху, выделяют крайний регистр. Ruby docs использовал пример p a if a = 0.zero?, тогда как Ruby Hacking Guide использовал эквивалентный пример p(lvar) if lvar = true, оба из которых поднимают NameError.

Sidenote: Помните = означает назначение, == означает сравнение.Конструкция if foo = true в крайнем случае указывает Ruby проверить, является ли выражение foo = true истинным.Другими словами, он присваивает значение от true до foo, а затем проверяет, равен ли результат этого присваивания true (будет).Это легко спутать с гораздо более распространенным if foo == true, который просто проверяет, равно ли foo сравнивается с true.Поскольку эти два понятия легко перепутать, Ruby выдаст предупреждение, если мы используем оператор присваивания в условном выражении: warning: found `= literal' in conditional, should be ==.

Используя утилиту ruby-parse, давайте сравним исходный пример foo = 'bar' if false с этим краевым регистром foo if foo = true:

> ruby-parse -L -e "foo = 'bar' if false"
s(:if,
  s(:false),
  s(:lvasgn, :foo,
    s(:str, "bar")), nil)
foo = 'bar' if false
            ~~ keyword         
~~~~~~~~~~~~~~~~~~~~ expression
s(:false)
foo = 'bar' if false
               ~~~~~ expression
s(:lvasgn, :foo,
  s(:str, "bar"))
foo = 'bar' if false     # Line 13
~~~ name                 # <-- `foo` is a name
    ~ operator        
~~~~~~~~~~~ expression
s(:str, "bar")
foo = 'bar' if false
          ~ end
      ~ begin         
      ~~~~~ expression

Как вы можете видеть выше в строках 13 и 14выходных данных в исходном примере foo - это имя (то есть переменная).

> ruby-parse -L -e "foo if foo = true"
s(:if,
  s(:lvasgn, :foo,
    s(:true)),
  s(:send, nil, :foo), nil)
foo if foo = true
    ~~ keyword              
~~~~~~~~~~~~~~~~~ expression
s(:lvasgn, :foo,
  s(:true))
foo if foo = true         # Line 10
       ~~~ name           # <-- `foo` is a name
           ~ operator       
       ~~~~~~~~~~ expression
s(:true)
foo if foo = true
             ~~~~ expression
s(:send, nil, :foo)
foo if foo = true         # Line 18
~~~ selector              # <-- `foo` is a selector
~~~ expression

В примере с ребром второй foo также является переменной (строки 10 и 11), нокогда мы смотрим на строки 18 и 19, мы видим, что первый foo идентифицирован как селектор (то есть метод).


Это показывает, что именно парсер решает, является ли вещьметод или переменную, и что она анализирует строку в другом порядке, чем то, как она будет вычислена позже.

Учитывая крайний случай ...

Когда выполняется анализатор:

  • сначала он видит всю строку как одно выражение
  • затем разбивает ее на два выражения, разделенных ключевым словом if
  • первое выражение foo начинается сстрочная буква, поэтому это должен быть метод или переменная.Это не существующая переменная, и за ней НЕ следует оператор присваивания, поэтому анализатор заключает, что это должен быть метод
  • второе выражение foo = true разбито на выражение, оператор, выражение. Опять же, выражение foo также начинается со строчной буквы, поэтому оно должно быть методом или переменной. Это не существующая переменная, но за ней следует оператор присваивания, поэтому парсер знает, как добавить ее в список локальных переменных.

Позже, когда запускается оценщик:

  • сначала будет присвоено true foo
  • затем он выполнит условие и проверит, является ли результат этого присваивания истинным (в данном случае это так)
  • Затем он вызовет метод foo (который вызовет NameError, если мы не обработаем его с помощью method_missing).
...