获取先前范围的局部变量

时间:2017-04-27 10:56:41

标签: ruby scope ruby-1.9.3

我正在创建一个在应用程序中使用的Ruby REPL。我做了代码:

a = 1
b = 2
currentScope = []
Kernel.local_variables.each do |var|
    currentScope << [var,Kernel.eval(var.to_s)]
end
launchREPL(currentScope)

在REPL中,我可以执行以下代码:

@a     #=>1
@a+@b  #=>3

理想情况下,在启动REPL之前,我不必编写四行代码,而是希望在launchREPL函数内运行它们。但是,这需要从launchREPL函数内部访问前一个范围。

测试1

最值得注意的是我尝试过:

launchREPL(Kernel)

当我执行以下操作时:

def launchREPL(scope)
    F = 0
    puts scope.local_variables # => [:F]
end

很明显这种方法无效。

的Test2

launchREPL(Kernel.binding)

def launchREPL(scope)
    Kernel.binding.local_variables #= Error: private method 'local_variables' called for #<Binding>
end

有什么办法可以做我想做的事吗?

编辑:P.S。这是当前launchREPL中的代码:

def launchREPL(scope=nil,winName="Ruby REPL")
    # ICM RB file Begin:
    puts "\"Starting REPL...\""
    __b = binding   #Evaluating in a binding, keeps track of local variables
    __s = ""


    ###############################################################################
    # SEND INSTANCE VARIABLES TO REPL
    ###############################################################################
    #
    #How to prepare scope
    #   currentScope = []
    #   Kernel.local_variables.each do |var|
    #       currentScope << [var,Kernel.eval(var.to_s)]
    #   end
    #   launchREPL(currentScope)

    if scope != nil
        scope.each do |varDef|
            __b.instance_variable_set "@#{varDef[0].to_s}" , varDef[1]
            __b.eval("@#{varDef[0].to_s} = __b.instance_variable_get(:@#{varDef[0].to_s})")
        end
    end

    # to get instance variables: __b.instance_variable_get(__b.instance_variables[0])
    # or better:                 __b.instance_variable_get(:@pipe1)
    #
    ###############################################################################

    bStartup = true
    while bStartup || __s != ""
        # If startup required skip evaluation step
        if !bStartup

            #Evaluate command
            begin
                __ret = __s + "\n>" + __b.eval(__s).to_s
            rescue 
                __ret = __s + "\n> Error: " + $!.to_s
            end
            puts __ret
        else
            #REPL is already running
            bStartup = false
        end

        #Read user input & print previous output
        __s = WSApplication.input_box(__ret,winName,"")
        __s == nil ? __s = "" : nil
    end
end

2 个答案:

答案 0 :(得分:3)

虽然你想要实现的目标尚不清楚,并且肯定有很多方法可以正确地完成,但可能会使用Object#send方法调用每个 ruby​​方法:

def launchREPL(scope)
  scope.send :local_variables #⇒ here you go
end

a = 42
launchREPL(binding).include?(:a)
#⇒ true

旁注:这就是你的“4行”通常用ruby写的方式:

local_variables.map { |var| [var, eval(var.to_s)] }

这就是应该如何编写(注意Binding#local_variable_get):

local_variables.map { |var| [var, binding.local_variable_get(var)] }

总结:

def launchREPL(scope)
  vars = scope.send(:local_variables).map do |var|
           [var, scope.local_variable_get(var)]
         end
  # some other code
end
a = 42
launchREPL(binding).to_h[:a]
#⇒ 42

答案 1 :(得分:0)

这不符合评论,所以我会将其作为答案发布。

def launchREPL(scope = nil, winName = "Ruby REPL")
  puts '"Starting REPL..."'

  scope.eval('local_variables').each do |var|
    instance_variable_set "@#{var}", scope.eval(var.to_s)
  end if scope

  s = ""
  loop do
    ret = begin
            "#{s}\n> #{eval(s)}"
          rescue => e
            "#{s}\n> Error: #{e.message}"
          end
    puts ret
    # s = WSApplication.input_box(ret, winName, "")
    # break if s.empty?
    s = "100 * @a" # remove this line and uncomment 2 above
  end
end

a = 42
launchREPL(binding)

这就是你应该编写函数的方法(我只是把它看作ruby代码。)上面的工作(目前它根本没有break,但你可以看到它在计算{{1}无限地。)