Skip to content

Instantly share code, notes, and snippets.

@cho45 cho45/guard_scope.rb
Created Feb 20, 2014

Embed
What would you like to do?
require 'continuation'
class Guard
attr_reader :args
attr_reader :called
attr_reader :result
def initialize(&before_finish)
@args = nil
@guard_cc = nil
@block_cc = nil
@finish_cc = nil
@before_finish = before_finish
end
def finish(result=nil)
@before_finish.call(self)
cc = callcc {|cc| cc }
if cc.is_a? Continuation
@finish_cc = cc
@result = result
@called = true
@block_cc.call(@result) # return finish block
else
cc
end
end
private
def return_guard
@guard_cc.call(self)
end
def return_finish(ret)
@finish_cc.call(ret)
end
end
def guard_scope(&block)
context = self
guards = []
current_guard = nil
orig_guard = begin context.singleton_class.method(:guard) rescue NameError; nil end
orig_block = begin context.singleton_class.method(:block) rescue NameError; nil end
context.define_singleton_method(:guard) do |&b|
cc = callcc {|cc| cc }
if cc.is_a? Continuation
current_guard = Guard.new do |g|
current_guard = g
end
current_guard.instance_variable_set(:@guard_cc, cc)
# Expect &block syntax and
# A method in block call block's lambda to get args
# and return guard
ret = b.call
if current_guard.instance_variable_get(:@block_cc)
# After finish of a guard
# current_guard is set by finish (block passed to Guard.new)
g = current_guard
current_guard = nil
g.send(:return_finish, ret)
else
raise "guard {} without calling &block"
end
else
current_guard = nil
cc
end
end
context.define_singleton_method(:block) do
count = 0
lambda {|*args|
raise "block is called more than once" if count > 0
count += 1
cc = callcc {|c| c }
if cc.is_a? Continuation
current_guard.instance_variable_set(:@args, args)
current_guard.instance_variable_set(:@block_cc, cc)
guards.unshift(current_guard)
current_guard.send(:return_guard)
else
cc
end
}
end
block_value = context.instance_eval(&block)
if orig_guard
context.define_singleton_method(:guard, &orig_guard)
else
context.singleton_class.send(:remove_method, :guard)
end
if orig_block
context.define_singleton_method(:block, &orig_block)
else
context.singleton_class.send(:remove_method, :block)
end
guards.each do |g|
g.finish(nil) unless g.called
end
block_value
end
require "rspec"
RSpec::Core::Runner.autorun
describe Guard do
foo = nil
events = nil
before do
foo = Object.new
events = []
foo.define_singleton_method(:transaction) do |label, &block|
events << [:before, label]
ret = block.call(label)
events << [:after, label, ret]
[:return, label]
end
end
it "should do nothing without guard()" do
ret = guard_scope do
true
end
expect(ret).to eq(true)
end
it "should extract block to flat" do
ret = guard_scope do
events << [:enter, :inner_block]
g1 = guard { foo.transaction(:label1, &block) }
events << [:leave, :inner_block]
true
end
expect(events).to eq([
[:enter, :inner_block],
[:before, :label1],
[:leave, :inner_block],
[:after, :label1, nil]
])
expect(ret).to eq(true)
end
it "should extract block to flat" do
ret = guard_scope do
events << [:enter, :inner_block]
g1 = guard { foo.transaction(:label1, &block) }
events << [:finish, g1.finish(:yield)]
events << [:leave, :inner_block]
true
end
expect(events).to eq([
[:enter, :inner_block],
[:before, :label1],
[:after, :label1, :yield],
[:finish, [:return, :label1]],
[:leave, :inner_block]
])
expect(ret).to eq(true)
end
it "should treat multiple guard block" do
ret = guard_scope do
g1 = guard { foo.transaction(:label1, &block) }
g2 = guard { foo.transaction(:label2, &block) }
true
end
expect(ret).to eq(true)
end
it "should not change context" do
@foo = []
guard_scope do
g1 = guard { foo.transaction(:label1, &block) }
@foo << :foo
end
expect(@foo).to eq([ :foo ])
end
it "should treat Guard#finish as returned value" do
ret = guard_scope do
g1 = guard { foo.transaction(:label1, &block) }
g2 = guard { foo.transaction(:label2, &block) }
g1.finish
end
expect(ret).to eq([ :return, :label1 ])
end
it "should raise error on guard {} without &block" do
expect {
guard_scope do
g1 = guard {}
end
}.to raise_error("guard {} without calling &block")
end
it "should raise error on block is called multiply" do
expect {
guard_scope do
g1 = guard { [1, 2, 3].each(&block) }
end
}.to raise_error("block is called more than once")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.