Skip to content

Commit

Permalink
Added local variable methods to Binding
Browse files Browse the repository at this point in the history
This adds the following methods:

* Binding#local_variable_set
* Binding#local_variable_get
* Binding#local_variable_defined?

Fixes #2992
Closes #3372
  • Loading branch information
Yorick Peterse committed Feb 5, 2016
1 parent 391db8d commit 40f04c1
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 0 deletions.
55 changes: 55 additions & 0 deletions core/binding.rb
Expand Up @@ -58,4 +58,59 @@ def eval(expr, filename=nil, lineno=nil)
def local_variables
variables.local_variables
end

def local_variable_set(name, value)
unless name.is_a?(Symbol)
name = Rubinius::Type.coerce_to(name, String, :to_str).to_sym
end

vars = variables

# If a local variable is defined in a parent scope we should update the
# variable in said scope and all child scopes, instead of _only_ setting it
# in the current scope.
while vars
meth = vars.method

if meth.local_names.include?(name)
return vars.set_local(meth.local_slot(name), value)
elsif vars.eval_local_defined?(name)
return vars.set_eval_local(name, value)
end

vars = vars.parent
end

variables.set_eval_local(name, value)
end

def local_variable_get(name)
unless name.is_a?(Symbol)
name = Rubinius::Type.coerce_to(name, String, :to_str).to_sym
end

vars = variables

while vars
meth = vars.method

if meth.local_names.include?(name)
return vars.locals[meth.local_slot(name)]
elsif vars.eval_local_defined?(name)
return vars.get_eval_local(name)
end

vars = vars.parent
end

raise NameErrror, "local variable #{name.inspect} not defined for #{inspect}"
end

def local_variable_defined?(name)
unless name.is_a?(Symbol)
name = Rubinius::Type.coerce_to(name, String, :to_str).to_sym
end

variables.local_defined?(name)
end
end
2 changes: 2 additions & 0 deletions core/variable_scope.rb
Expand Up @@ -136,6 +136,8 @@ def local_defined?(name)
return true if vars.eval_local_defined?(name, false)
vars = vars.parent
end

false
end

def local_layout
Expand Down
50 changes: 50 additions & 0 deletions spec/ruby/core/binding/local_variable_defined_p_spec.rb
@@ -0,0 +1,50 @@
require File.expand_path('../../../spec_helper', __FILE__)

describe 'Binding#local_variable_defined?' do
it 'returns false when a variable is not defined' do
binding.local_variable_defined?(:foo).should == false
end

it 'returns true when a regular local variable is defined' do
foo = 10

binding.local_variable_defined?(:foo).should == true
end

it 'returns true when a local variable is defined using eval()' do
bind = binding

bind.eval('foo = 10')

bind.local_variable_defined?(:foo).should == true
end

it 'returns true when a local variable is defined using Binding#local_variable_set' do
bind = binding

bind.local_variable_set(:foo, 10)

bind.local_variable_defined?(:foo).should == true
end

it 'returns true when a local variable is defined in a parent scope' do
foo = 10

proc { binding.local_variable_defined?(:foo) }.call.should == true
end

it 'allows usage of a String as the variable name' do
foo = 10

binding.local_variable_defined?('foo').should == true
end

it 'allows usage of an object responding to #to_str as the variable name' do
foo = 10
name = mock(:obj)

name.stub!(:to_str).and_return('foo')

binding.local_variable_defined?(name).should == true
end
end
34 changes: 34 additions & 0 deletions spec/ruby/core/binding/local_variable_get_spec.rb
@@ -0,0 +1,34 @@
require File.expand_path('../../../spec_helper', __FILE__)

describe 'Binding#local_variable_get' do
it 'gets a local variable defined before the Binding' do
number = 10

binding.local_variable_get(:number).should == 10
end

it 'gets a local variable defined in the Binding' do
bind = binding

bind.local_variable_set(:number, 10)
bind.local_variable_get(:number).should == 10
end

it 'gets a local variable defined using eval()' do
bind = binding

bind.eval('number = 10')

bind.local_variable_get(:number).should == 10
end

it 'gets a local variable defined in a parent scope' do
number = 10

proc { binding.local_variable_get(:number) }.call.should == 10
end

it 'raises NameError for an undefined local variable' do
proc { binding.local_variable_get(:cats) }.should raise_error(NameError)
end
end
54 changes: 54 additions & 0 deletions spec/ruby/core/binding/local_variable_set_spec.rb
@@ -0,0 +1,54 @@
require File.expand_path('../../../spec_helper', __FILE__)

describe 'Binding#local_variable_set' do
it 'sets a new local variable' do
bind = binding

bind.local_variable_set(:number, 10)
bind.local_variable_get(:number).should == 10
end

it 'sets a local variable using a String as the variable name' do
bind = binding

bind.local_variable_set('number', 10)
bind.local_variable_get('number').should == 10
end

it 'sets a local variable using an object responding to #to_str as the variable name' do
bind = binding
name = mock(:obj)

name.stub!(:to_str).and_return('number')

bind.local_variable_set(name, 10)
bind.local_variable_get(name).should == 10
end

it 'scopes new local variables to the receiving Binding' do
bind = binding

bind.local_variable_set(:number, 10)

proc { number }.should raise_error(NameError)
end

it 'overwrites an existing local variable defined before a Binding' do
number = 10
bind = binding

bind.local_variable_set(:number, 20)

number.should == 20
end

it 'overwrites a local variable defined using eval()' do
bind = binding

bind.eval('number = 10')

bind.local_variable_set(:number, 20)

bind.local_variable_get(:number).should == 20
end
end

0 comments on commit 40f04c1

Please sign in to comment.