# # Join-Patterns for Ruby # by Michael Neumann # $Id: join.rb,v 1.4 2001/04/12 13:57:55 michael Exp $ # # # Other behaviour than JoCaml: # # * when sending to a channel, you have to append the ! on asynchronous channels # * you can define e.g. "loop" and "loop!", both are to different channels # * you can call asynchronous channels outside "spawn" # # # short form for Channel.new # def channel(name, *params) Channel.new(name.to_s, *params) end class JoinSpace attr_reader :ts, :mutex, :channel_names def initialize(tuplespace) @ts = tuplespace @threads = [] @mutex = Mutex.new @channel_names = [] @proxy = Object.new end # # Spawns a new thread executing the given block # for analogy of JoCaml # def spawn @threads << Thread.start { yield } end # # waits till all threads have finished # def wait_for_threads @threads.each {|t| t.join} end def join(*channels,&code) j = Join.new(self, *channels, &code) Thread.start { loop { j.run } } channels.each {|c| @channel_names << c.name } @channel_names.uniq! end IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/ # # same as join, only that the channels # are given as a string (looks better) # def let_def(str, &code) channels = str.split("|") channels = channels.collect do |chan| if chan =~ /^\s*([A-Za-z][A-Za-z0-9_]*!?)\s*(.*?)\s*$/ channel_name = $1 params = $2 if params =~ /^\((.*?)\)$/ then channel_args = $1.split(",").collect {|a| a.strip} # check if each channel-argument has correct syntax channel_args.each do |a| unless a =~ IDENTIFIER raise "syntax error in channel parameter definitions: #{chan}" end end elsif params =~ IDENTIFIER # one argument without parentheses channel_args = [params] else raise "syntax error of channel definition: #{chan}" end # create new channel Channel.new(channel_name, *channel_args) else raise "syntax error of channel definition: #{chan}" end end # collect join(*channels, &code) end def send(name, *params) name = name.to_s if name[-1].chr == "!" then # asynchronous call @ts.out [name, *params] nil else id = Object.new # create unique id @ts.out [name, id, *params] @ts.in( ["=" + name, id, nil])[2] end end end # class JoinSpace class Join def initialize(joinspace, *channels, &code) @joinspace = joinspace @channels = channels @code = code # set all channels to the tuplespace of @joinspace @channels.each {|c| c.ts = @joinspace.ts } if @code.nil? raise "No code specified" end end def run res = {} loop { @joinspace.mutex.lock ok = true @channels.each do |c| if c.ready == false ok = false break end end if ok then @channels.each {|c| c.run(res) } @joinspace.mutex.unlock break end @joinspace.mutex.unlock } # call code block cc = CallingContext.new(self, res, @code) cc.execute end # forwards the call to JoinSpace def send(*n) @joinspace.send(*n) end # return a value to a synchronous channel def reply_to(name, value=nil) name = name.to_s if name[-1].chr == "!" then raise "cannot reply to an asynchronous channel" end # find corresponding id id = @channels.find { |c| c.name == name }.id @joinspace.ts.out ["=" + name, id, value] end # # where the code block of a channel is executed # class CallingContext def initialize(join, variable_settings, code) @__join = join @__code = code variable_settings.each {|k,v| instance_eval "@#{k} = v" } end def execute instance_eval &(@__code) end # forward to Join def send(*n) @__join.send(*n) end # forward to Join def reply_to(*n) @__join.reply_to(*n) end end # class CallingContext end # class Join class Channel attr_reader :name attr_reader :id attr_writer :ts def initialize(name, *params) @name = name @params = params @ts = nil # tuplespace is initialized by Join.new end def ready begin num = @params.size num += 1 if @name[-1].chr != "!" @ts.rd ([@name, *([nil] * num)],true) rescue ThreadError return false end true end def run(res) if @name[-1].chr == "!" then params = @ts.in ([@name, *([nil] * @params.size)]) start = 1 else params = @ts.in ([@name, nil, *([nil] * @params.size)]) @id = params[1] start = 2 end for i in 0...(@params.size) res[@params[i]] = params[i+start] end end end # class Channel ######################################################## # MAIN PROGRAM ######################################################## if __FILE__ == $0 then require "tuplespace" ts = TupleSpace.new js = JoinSpace.new(ts) #js.join channel("loop!", :a, :x) do js.let_def "loop! (a, x)" do if @x > 0 then @a.call send "loop!", @a, @x-1 end end js.let_def "count! n | inc ()" do send "count!", @n + 1 reply_to "inc" end #js.join channel ("count!", :n), channel ("get") do js.let_def "count! n | get ()" do send "count!", @n reply_to "get", @n end js.send "count!", 4 js.send "inc" js.send "inc" js.send "inc" js.send "inc" js.send "inc" x = js.send "get" p x js.send "loop!", proc { puts "*" }, 5 sleep 2 js.wait_for_threads p js.channel_names end