Sancarn
Sancarn

Reputation: 2824

Get local variables of previous scope

I am making a Ruby REPL to be used inside an application. I made code:

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

Inside the REPL, I can execute the following code:

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

Ideally I wouldn't have to write the four lines of code before I launch the REPL, and instead I would like to run them inside the launchREPL function. However this would require access to the previous scope from inside the launchREPL function.


Test1

Most notably I tried:

launchREPL(Kernel)

When I do the following:

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

it is apparent that this method is not valid.

Test2

launchREPL(Kernel.binding)

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

Is there any way to do what I'm trying to do?


Edit: P.S. This is currently the code inside 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

Upvotes: 3

Views: 255

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

This won’t fit the comment, so I would post it as an answer.

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)

This is how your function should be written (I have just make it looking as ruby code.) The above works (currently it has no break at all, but you can see as it’s calculating 4200 infinitely.)

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

Although what you are trying to achieve is unclear and there are definitely many ways to do it properly, every ruby method might be called with Object#send approach:

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

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

Sidenote: this is how your “4 lines” are usually written in ruby:

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

And this is how they should be written (note Binding#local_variable_get):

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

The summing up:

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

Upvotes: 3

Related Questions