Есть ли способ получить доступ к локальной переменной, определенной внутри блока за пределами блока? - PullRequest
1 голос
/ 18 июня 2019

Я написал простой код, который оценивает кусок кода и записывает вывод в файл. Таким образом, это уменьшает некоторые из моих, потому что мне нужно много-много файлов с возвращаемыми значениями внутри них для каждой строки!.

В любом случае код, с которым я работаю:

#!/usr/bin/ruby -w

def create(file, code)
    f = code.strip.each_line.map { |cd| cd.strip.then { |c| [c, "# => #{binding.eval(c)}"] } }
    max_length = f.map { |x| x[0].length }.max + 4
    f.map { |v| v[0].ljust(max_length) << v[1] }.join("\n").tap { |data| File.write(file, data + "\n") }
end

puts create(
    File.join(__dir__, 'p.rb'),
    <<~'EOF'
        foo = 1
        bar = 2
        baz, qux = 5, 3
    EOF
)

В этом случае файл p.rb записывается. Содержание p.rb:

foo = 1            # => 1
bar = 2            # => 2
baz, qux = 5, 3    # => [5, 3]

Но проблема возникает, когда я хочу значение переменной. Например:

puts create(
    File.join(__dir__, 'p.rb'),
    <<~'EOF'
        baz, qux = 5, 3
        [baz, qux]
    EOF
)

Выход:

/tmp/aa.rb:4:in `block (2 levels) in create': undefined local variable or method `baz' for main:Object (NameError)
    from /tmp/aa.rb:4:in `eval'
    from /tmp/aa.rb:4:in `block (2 levels) in create'
    from /tmp/aa.rb:4:in `then'
    from /tmp/aa.rb:4:in `block in create'
    from /tmp/aa.rb:4:in `each_line'
    from /tmp/aa.rb:4:in `each'
    from /tmp/aa.rb:4:in `map'
    from /tmp/aa.rb:4:in `create'
    from /tmp/aa.rb:9:in `<main>'

Ранее я работал в некоторых графических играх, которые тоже делают подобные вещи после прочтения файла конфигурации, но там я использовал для определения переменных либо глобальные переменные (просто добавьте $ перед объявлением переменной), либо просто использовал переменные экземпляра в верхний объект самообороны.

Но есть ли способ обойти проблему, с которой я сейчас сталкиваюсь? Могу ли я определить переменные в связывании или некоторые подобные хаки?

1 Ответ

3 голосов
/ 18 июня 2019

binding возвращает новый экземпляр каждый раз, когда вы вызываете его.Вы должны отправить eval в ту же привязку, чтобы получить доступ к локальным переменным, которые вы создали ранее:

def create(code, b = binding)
  width = code.each_line.map(&:length).max
  code.each_line.map do |line|
    '%-*s   #=> %s' % [width, line.chomp, b.eval(line)]
  end
end

puts create <<~'RUBY'
  baz, qux = 5, 3
  baz
  qux
RUBY

Вывод:

baz, qux = 5, 3    #=> [5, 3]
baz                #=> 5
qux                #=> 3

Обратите внимание, что в приведенном выше примере, binding сделает локальные переменные метода доступными для блока:

create 'local_variables'
#=> ["local_variables   #=> [:code, :b, :width]"]

Возможно, вы захотите создать более ограниченный контекст оценки, например (репликация основного Ruby)

def empty_binding
  Object.allocate.instance_eval do
    class << self
      def to_s
        'main'
      end
      alias inspect to_s
    end
    return binding
  end
end

def create(code, b = empty_binding)
  # ...
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...