Skip to content

Instantly share code, notes, and snippets.

@cho45
Created February 20, 2014 12:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cho45/9112385 to your computer and use it in GitHub Desktop.
Save cho45/9112385 to your computer and use it in GitHub Desktop.
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