Created
February 20, 2014 12:25
-
-
Save cho45/9112385 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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