Binding 클래스
Binding 클래스
Binding 클래스의 객체는 코드의 특정 위치에서의 실행 컨텍스트를 캡슐화하고, 나중에 사용할 수 있도록 이 컨텍스트를 보존해요. 그 컨텍스트에서 접근 가능한 변수, 메서드, self의 값, 그리고 가능하면 반복자 블록까지 모두 보존돼요. Binding 객체는 Kernel#binding으로 만들 수 있고, Kernel#set_trace_func의 콜백과 TracePoint 인스턴스에서도 접근할 수 있어요.
이 binding 객체는 Kernel#eval 메서드의 두 번째 인자로 전달돼 평가의 환경을 확립할 수 있어요.
class Demo
def initialize(n)
@secret = n
end
def get_binding
binding
end
end
k1 = Demo.new(99)
b1 = k1.get_binding
k2 = Demo.new(-3)
b2 = k2.get_binding
eval("@secret", b1) #=> 99
eval("@secret", b2) #=> -3
eval("@secret") #=> nil
Binding 객체에는 클래스 고유의 메서드가 따로 없어요.
출처: Ruby 3.3 API
본문
eval(string [, filename [,lineno]]) → obj
string의 Ruby 표현식(들)을 binding의 컨텍스트에서 평가해요. 선택적 filename과 lineno 파라미터가 있으면 문법 오류를 보고할 때 사용돼요.
def get_binding(param)
binding
end
b = get_binding("hello")
b.eval("param") #=> "hello"
static VALUE
bind_eval(int argc, VALUE *argv, VALUE bindval)
{
VALUE args[4];
rb_scan_args(argc, argv, "12", &args[0], &args[2], &args[3]);
args[1] = bindval;
return rb_f_eval(argc+1, args, Qnil /* self will be searched in eval */);
}
irb(show_code: true)
binding.irb가 호출된 곳에서 IRB 세션을 열어 인터랙티브 디버깅을 가능하게 해요. 현재 스코프에서 사용 가능한 메서드나 변수를 호출할 수 있고, 필요하다면 상태를 변경할 수도 있어요.
potato.rb라는 Ruby 파일에 다음 코드가 있다고 해볼게요.
class Potato
def initialize
@cooked = false
binding.irb
puts "Cooked potato: #{@cooked}"
end
end
Potato.new
ruby potato.rb를 실행하면 binding.irb가 호출되는 곳에서 IRB 세션이 열리고, 다음처럼 보여요.
$ ruby potato.rb
From: potato.rb @ line 4 :
1: class Potato
2: def initialize
3: @cooked = false
=> 4: binding.irb
5: puts "Cooked potato: #{@cooked}"
6: end
7: end
8:
9: Potato.new
irb(#<Potato:0x00007feea1916670>):001:0>
유효한 Ruby 코드를 아무거나 입력하면 그 코드가 현재 컨텍스트에서 평가돼요. 덕분에 코드를 반복 실행하지 않고도 디버깅할 수 있어요.
irb(#<Potato:0x00007feea1916670>):001:0> @cooked
=> false
irb(#<Potato:0x00007feea1916670>):002:0> self.class
=> Potato
irb(#<Potato:0x00007feea1916670>):003:0> caller.first
=> ".../2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'"
irb(#<Potato:0x00007feea1916670>):004:0> @cooked = true
=> true
exit 명령으로 IRB 세션을 빠져나올 수 있어요. 빠져나오면 binding.irb가 멈춰둔 지점부터 실행이 재개돼요.
irb(#<Potato:0x00007feea1916670>):005:0> exit
Cooked potato: true
자세한 내용은 IRB를 참고하세요.
# File lib/irb.rb, line 1578
def irb(show_code: true)
# Setup IRB with the current file's path and no command line arguments
IRB.setup(source_location[0], argv: []) unless IRB.initialized?
# Create a new workspace using the current binding
workspace = IRB::WorkSpace.new(self)
# Print the code around the binding if show_code is true
STDOUT.print(workspace.code_around_binding) if show_code
# Get the original IRB instance
debugger_irb = IRB.instance_variable_get(:@debugger_irb)
irb_path = File.expand_path(source_location[0])
if debugger_irb
# If we're already in a debugger session, set the workspace and irb_path for the original IRB instance
debugger_irb.context.replace_workspace(workspace)
debugger_irb.context.irb_path = irb_path
# If we've started a debugger session and hit another binding.irb, we don't want
# to start an IRB session instead, we want to resume the irb:rdbg session.
IRB::Debug.setup(debugger_irb)
IRB::Debug.insert_debug_break
debugger_irb.debug_break
else
# If we're not in a debugger session, create a new IRB instance with the current
# workspace
binding_irb = IRB::Irb.new(workspace, from_binding: true)
binding_irb.context.irb_path = irb_path
binding_irb.run(IRB.conf)
binding_irb.debug_break
end
end
local_variable_defined?(symbol) → obj
지역 변수 symbol이 존재하면 true를 반환해요.
def foo
a = 1
binding.local_variable_defined?(:a) #=> true
binding.local_variable_defined?(:b) #=> false
end
이 메서드는 다음 코드의 축약형이에요.
binding.eval("defined?(#{symbol}) == 'local-variable'")
static VALUE
bind_local_variable_defined_p(VALUE bindval, VALUE sym)
{
ID lid = check_local_id(bindval, &sym);
const rb_binding_t *bind;
const rb_env_t *env;
if (!lid) return Qfalse;
GetBindingPtr(bindval, bind);
env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block));
return RBOOL(get_local_variable_ptr(&env, lid));
}
local_variable_get(symbol) → obj
지역 변수 symbol의 값을 반환해요.
def foo
a = 1
binding.local_variable_get(:a) #=> 1
binding.local_variable_get(:b) #=> NameError
end
이 메서드는 다음 코드의 축약형이에요.
binding.eval("#{symbol}")
static VALUE
bind_local_variable_get(VALUE bindval, VALUE sym)
{
ID lid = check_local_id(bindval, &sym);
const rb_binding_t *bind;
const VALUE *ptr;
const rb_env_t *env;
if (!lid) goto undefined;
GetBindingPtr(bindval, bind);
env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block));
if ((ptr = get_local_variable_ptr(&env, lid)) != NULL) {
return *ptr;
}
sym = ID2SYM(lid);
undefined:
rb_name_err_raise("local variable `%1$s' is not defined for %2$s",
bindval, sym);
UNREACHABLE_RETURN(Qundef);
}
local_variable_set(symbol, obj) → obj
symbol이라는 지역 변수를 obj로 설정해요.
def foo
a = 1
bind = binding
bind.local_variable_set(:a, 2) # set existing local variable `a'
bind.local_variable_set(:b, 3) # create new local variable `b'
# `b' exists only in binding
p bind.local_variable_get(:a) #=> 2
p bind.local_variable_get(:b) #=> 3
p a #=> 2
p b #=> NameError
end
이 메서드는 obj가 Ruby 코드로 덤프될 수 있다면 다음 코드와 비슷하게 동작해요.
binding.eval("#{symbol} = #{obj}")
static VALUE
bind_local_variable_set(VALUE bindval, VALUE sym, VALUE val)
{
ID lid = check_local_id(bindval, &sym);
rb_binding_t *bind;
const VALUE *ptr;
const rb_env_t *env;
if (!lid) lid = rb_intern_str(sym);
GetBindingPtr(bindval, bind);
env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block));
if ((ptr = get_local_variable_ptr(&env, lid)) == NULL) {
/* not found. create new env */
ptr = rb_binding_add_dynavars(bindval, bind, 1, &lid);
env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block));
}
#if YJIT_STATS
rb_yjit_collect_binding_set();
#endif
RB_OBJ_WRITE(env, ptr, val);
return val;
}
local_variables → Array
binding의 지역 변수 이름을 심볼로 반환해요.
def foo
a = 1
2.times do |n|
binding.local_variables #=> [:a, :n]
end
end
이 메서드는 다음 코드의 축약형이에요.
binding.eval("local_variables")
static VALUE
bind_local_variables(VALUE bindval)
{
const rb_binding_t *bind;
const rb_env_t *env;
GetBindingPtr(bindval, bind);
env = VM_ENV_ENVVAL_PTR(vm_block_ep(&bind->block));
return rb_vm_env_local_variables(env);
}
receiver → object
binding 객체에 묶인(bound) 리시버를 반환해요.
static VALUE
bind_receiver(VALUE bindval)
{
const rb_binding_t *bind;
GetBindingPtr(bindval, bind);
return vm_block_self(&bind->block);
}
source_location → [String, Integer]
binding 객체의 Ruby 소스 파일명과 줄 번호를 반환해요.
static VALUE
bind_location(VALUE bindval)
{
VALUE loc[2];
const rb_binding_t *bind;
GetBindingPtr(bindval, bind);
loc[0] = pathobj_path(bind->pathobj);
loc[1] = INT2FIX(bind->first_lineno);
return rb_ary_new4(2, loc);
}