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