[前][次][番号順一覧][スレッド一覧][生データ]

yarv-diff:312

From: ko1 atdot.net
Date: 27 Feb 2006 02:53:23 -0000
Subject: [yarv-diff:312] r477 - in trunk: . lib lib/racc lib/rinda

Author: aamine
Date: 2006-02-27 11:53:22 +0900 (Mon, 27 Feb 2006)
New Revision: 477

Added:
   trunk/lib/README
   trunk/lib/gserver.rb
   trunk/lib/parsearg.rb
   trunk/lib/racc/
   trunk/lib/racc/parser.rb
   trunk/lib/readbytes.rb
   trunk/lib/rinda/
   trunk/lib/rinda/.document
   trunk/lib/rinda/rinda.rb
   trunk/lib/rinda/ring.rb
   trunk/lib/rinda/tuplespace.rb
Modified:
   trunk/ChangeLog
Log:
* lib/README: imported from Ruby CVS trunk HEAD.
* lib/gserver.rb: ditto.
* lib/readbytes.rb: ditto.
* lib/parsearg.rb: ditto.
* lib/racc: ditto.
* lib/rinda: ditto.


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/ChangeLog	2006-02-27 02:53:22 UTC (rev 477)
@@ -4,6 +4,21 @@
 #  from Mon, 03 May 2004 01:24:19 +0900
 #
 
+2006-02-27(Mon) 11:53:07 +0900  Minero Aoki  <aamine loveruby.net>
+
+	* lib/README: imported from Ruby CVS trunk HEAD.
+
+	* lib/gserver.rb: ditto.
+
+	* lib/readbytes.rb: ditto.
+
+	* lib/parsearg.rb: ditto.
+
+	* lib/racc: ditto.
+
+	* lib/rinda: ditto.
+
+
 2006-02-27(Mon) 11:27:19 +0900  Minero Aoki  <aamine loveruby.net>
 
 	* lib/thread.rb (Queue#pop): faster code. [yarv-dev:973]

Added: trunk/lib/README
===================================================================
--- trunk/lib/README	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/README	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,96 @@
+English.rb	lets Perl'ish global variables have English names
+Env.rb		loads importenv.rb
+README		this file
+base64.rb	encodes/decodes base64 (obsolete)
+benchmark.rb	a benchmark utility
+cgi-lib.rb	simple CGI support library (old style)
+cgi.rb		CGI support library
+cgi/session.rb	CGI session class
+complex.rb	complex number suppor
+csv.rb		CSV parser/generator
+date.rb		date object
+date/format.rb	date parsing and formatting
+date2.rb	date object (obsolete; use date)
+debug.rb	ruby debugger
+delegate.rb	delegates messages to other object
+drb.rb		distributed Ruby
+e2mmap.rb	exception utilities
+erb.rb		tiny eRuby library
+eregex.rb	extended regular expression (just a proof of concept)
+fileutils.rb	file utilities
+finalize.rb	adds finalizer to the object
+find.rb		traverses directory tree
+forwardable.rb	explicit delegation library
+ftools.rb	file tools
+getoptlong.rb	GNU getoptlong compatible
+getopts.rb	parses command line options (use getoptlong)
+gserver.rb	general TCP server
+importenv.rb	imports environment variables as global variables
+ipaddr.rb	defines the IPAddr class
+irb.rb		interactive ruby
+jcode.rb	Japanese text handling (replace String methods)
+logger.rb	simple logging utility
+mailread.rb	reads mail headers
+mathn.rb	extended math operation
+matrix.rb	matrix calculation library
+mkmf.rb		Makefile maker
+monitor.rb	exclusive region monitor for thread
+mutex_m.rb	mutex mixin
+net/ftp.rb	ftp access
+net/http.rb	HTTP access
+net/imap.rb	IMAP4 access
+net/pop.rb	POP3 access
+net/protocol.rb	abstract class for net library (DO NOT USE)
+net/smtp.rb	SMTP access
+net/telnet.rb	telnet library
+observer.rb	observer desing pattern library (provides Observable)
+open-uri.rb	easy-to-use network interface using URI and Net
+open3.rb	opens subprocess connection stdin/stdout/stderr
+optparse.rb	command line option analysis
+ostruct.rb	python style object
+parsearg.rb	argument parser using getopts
+parsedate.rb	parses date string
+pathname.rb	Object-Oriented Pathname Class
+ping.rb		checks whether host is up, using TCP echo.
+pp.rb		pretty print objects
+prettyprint.rb	pretty printing algorithm
+profile.rb	runs ruby profiler
+profiler.rb	ruby profiler module
+pstore.rb	persistent object strage using marshal
+racc/parser.rb	racc (Ruby yACC) runtime
+rational.rb	rational number support
+rdoc            source-code documentation tool
+readbytes.rb	define IO#readbytes
+resolv-replace.rb	replace Socket DNS by resolve.rb
+resolv.rb	DNS resolver in Ruby
+rexml		an XML parser for Ruby, in Ruby
+rubyunit.rb	original Ruby Unit testing framework
+scanf.rb	scanf for Ruby
+set.rb		defines the Set class
+shell.rb	runs commands and does pipeline operations like shell
+shellwords.rb	split into words like shell
+singleton.rb	singleton design pattern library
+soap		SOAP 1.1 implementation
+sync.rb		2 phase lock
+tempfile.rb	temporary file with automatic removal
+test/unit	Ruby Unit Testing Framework
+thread.rb	thread support
+thwait.rb	thread syncronization class
+time.rb		RFC2822, RFC2616, ISO8601 style time formatting/parsing
+timeout.rb	provides timeout
+tmpdir.rb	retrieve temporary directory path
+tracer.rb	execution tracer
+tsort.rb	topological sorting
+un.rb		Utilities to replace common UNIX commands in Makefiles etc
+uri.rb		URI support
+uri/ftp.rb	ftp scheme support
+uri/http.rb	http scheme support
+uri/https.rb	https scheme support
+uri/ldap.rb	ldap scheme support
+uri/mailto.rb	mailto scheme support
+weakref.rb	weak reference class
+webrick.rb	WEB server toolkit
+wsdl		WSDL 1.1 implementation
+xmlrpc		XML-RPC implementation
+xsd		XML Schema Datatypes implementation
+yaml.rb		YAML implementation

Added: trunk/lib/gserver.rb
===================================================================
--- trunk/lib/gserver.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/gserver.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,249 @@
+#
+# Copyright (C) 2001 John W. Small All Rights Reserved
+#
+# Author::        John W. Small
+# Documentation:: Gavin Sinclair
+# Licence::       Freeware.
+#
+# See the class GServer for documentation.
+#
+
+require "socket"
+require "thread"
+
+#
+# +GServer+ implements a generic server, featuring thread pool management, simple logging, and
+# multi-server management.  See <tt>xmlrpc/httpserver.rb</tt> in the Ruby standard library for
+# an example of +GServer+ in action.
+#
+# Any kind of application-level server can be implemented using this class.  It accepts
+# multiple simultaneous connections from clients, up to an optional maximum number.  Several
+# _services_ (i.e. one service per TCP port) can be run simultaneously, and stopped at any time
+# through the class method <tt>GServer.stop(port)</tt>.  All the threading issues are handled,
+# saving you the effort.  All events are optionally logged, but you can provide your own event
+# handlers if you wish.
+#
+# === Example
+#
+# Using +GServer+ is simple.  Below we implement a simple time server, run it, query it, and
+# shut it down.  Try this code in +irb+:
+#
+#   require 'gserver'
+#
+#   #
+#   # A server that returns the time in seconds since 1970.
+#   # 
+#   class TimeServer < GServer
+#     def initialize(port=10001, *args)
+#       super(port, *args)
+#     end
+#     def serve(io)
+#       io.puts(Time.now.to_i)
+#     end
+#   end
+#
+#   # Run the server with logging enabled (it's a separate thread).
+#   server = TimeServer.new
+#   server.audit = true                  # Turn logging on.
+#   server.start 
+#
+#   # *** Now point your browser to http://localhost:10001 to see it working ***
+#
+#   # See if it's still running. 
+#   GServer.in_service?(10001)           # -> true
+#   server.stopped?                      # -> false
+#
+#   # Shut the server down gracefully.
+#   server.shutdown
+#   
+#   # Alternatively, stop it immediately.
+#   GServer.stop(10001)
+#   # or, of course, "server.stop".
+#
+# All the business of accepting connections and exception handling is taken care of.  All we
+# have to do is implement the method that actually serves the client.
+#
+# === Advanced
+#
+# As the example above shows, the way to use +GServer+ is to subclass it to create a specific
+# server, overriding the +serve+ method.  You can override other methods as well if you wish,
+# perhaps to collect statistics, or emit more detailed logging.
+#
+#   connecting
+#   disconnecting
+#   starting
+#   stopping
+#
+# The above methods are only called if auditing is enabled.
+#
+# You can also override +log+ and +error+ if, for example, you wish to use a more sophisticated
+# logging system.
+#
+class GServer
+
+  DEFAULT_HOST = "127.0.0.1"
+
+  def serve(io)
+  end
+
+  @@services = {}   # Hash of opened ports, i.e. services
+  @@servicesMutex = Mutex.new
+
+  def GServer.stop(port, host = DEFAULT_HOST)
+      servicesMutex.synchronize {
+        services[host][port].stop
+    }
+  end
+
+  def GServer.in_service?(port, host = DEFAULT_HOST)
+      services.has_key?(host) and
+        services[host].has_key?(port)
+  end
+
+  def stop
+    @connectionsMutex.synchronize  {
+      if @tcpServerThread
+        @tcpServerThread.raise "stop"
+      end
+    }
+  end
+
+  def stopped?
+    @tcpServerThread == nil
+  end
+
+  def shutdown
+    @shutdown = true
+  end
+
+  def connections
+    @connections.size
+  end
+
+  def join
+    @tcpServerThread.join if @tcpServerThread
+  end
+
+  attr_reader :port, :host, :maxConnections
+  attr_accessor :stdlog, :audit, :debug
+
+  def connecting(client)
+    addr = client.peeraddr
+    log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " +
+        "#{addr[2]}<#{addr[3]}> connect")
+    true
+  end
+
+  def disconnecting(clientPort)
+    log("#{self.class.to_s} #{@host}:#{@port} " +
+      "client:#{clientPort} disconnect")
+  end
+
+  protected :connecting, :disconnecting
+
+  def starting()
+    log("#{self.class.to_s} #{@host}:#{@port} start")
+  end
+
+  def stopping()
+    log("#{self.class.to_s} #{@host}:#{@port} stop")
+  end
+
+  protected :starting, :stopping
+
+  def error(detail)
+    log(detail.backtrace.join("\n"))
+  end
+
+  def log(msg)
+    if @stdlog
+      @stdlog.puts("[#{Time.new.ctime}] %s" % msg)
+      @stdlog.flush
+    end
+  end
+
+  protected :error, :log
+
+  def initialize(port, host = DEFAULT_HOST, maxConnections = 4,
+    stdlog = $stderr, audit = false, debug = false)
+    @tcpServerThread = nil
+    @port = port
+    @host = host
+    @maxConnections = maxConnections
+    @connections = []
+    @connectionsMutex = Mutex.new
+    @connectionsCV = ConditionVariable.new
+    @stdlog = stdlog
+    @audit = audit
+    @debug = debug
+  end
+
+  def start(maxConnections = -1)
+    raise "running" if !stopped?
+    @shutdown = false
+    @maxConnections = maxConnections if maxConnections > 0
+      servicesMutex.synchronize  {
+      if GServer.in_service?(@port,@host)
+        raise "Port already in use: #{host}:#{@port}!"
+      end
+      @tcpServer = TCPServer.new(@host,@port)
+      @port = @tcpServer.addr[1]
+      @@services[@host] = {} unless   services.has_key?( host)
+      @@services[@host][@port] = self;
+    }
+    @tcpServerThread = Thread.new {
+      begin
+        starting if @audit
+        while !@shutdown
+          @connectionsMutex.synchronize  {
+             while @connections.size >= @maxConnections
+               @connectionsCV.wait(@connectionsMutex)
+             end
+          }
+          client = @tcpServer.accept
+          @connections << Thread.new(client)  { |myClient|
+            begin
+              myPort = myClient.peeraddr[1]
+              serve(myClient) if !@audit or connecting(myClient)
+            rescue => detail
+              error(detail) if @debug
+            ensure
+              begin
+                myClient.close
+              rescue
+              end
+              @connectionsMutex.synchronize {
+                @connections.delete(Thread.current)
+                @connectionsCV.signal
+              }
+              disconnecting(myPort) if @audit
+            end
+          }
+        end
+      rescue => detail
+        error(detail) if @debug
+      ensure
+        begin
+          @tcpServer.close
+        rescue
+        end
+        if @shutdown
+          @connectionsMutex.synchronize  {
+             while @connections.size > 0
+               @connectionsCV.wait(@connectionsMutex)
+             end
+          }
+        else
+          @connections.each { |c| c.raise "stop" }
+        end
+        @tcpServerThread = nil
+          servicesMutex.synchronize  {
+            services[ host].delete( port)
+        }
+        stopping if @audit
+      end
+    }
+    self
+  end
+
+end

Added: trunk/lib/parsearg.rb
===================================================================
--- trunk/lib/parsearg.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/parsearg.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,85 @@
+#
+#		parsearg.rb - parse arguments
+#			$Release Version: $
+#			$Revision: 1.5 $
+#			$Date: 2006/02/03 09:15:37 $
+#			by Yasuo OHBA(SHL Japan Inc. Technology Dept.)
+#
+# --
+#
+#	
+#
+
+warn "Warning:#{caller[0].sub(/:in `.*'\z/, '')}: parsearg is deprecated after Ruby 1.8.1; use optparse instead"
+
+$RCS_ID=%q$Header: /var/cvs/ruby/ruby/lib/parsearg.rb,v 1.5 2006/02/03 09:15:37 matz Exp $
+
+require "getopts"
+
+def printUsageAndExit()
+  if $USAGE
+    eval($USAGE)
+  end
+  exit()
+end
+
+def setParenthesis(ex, opt, c)
+  if opt != ""
+    ex = sprintf("%s$OPT_%s%s", ex, opt, c)
+  else
+    ex = sprintf("%s%s", ex, c)
+  end
+  return ex
+end
+
+def setOrAnd(ex, opt, c)
+  if opt != ""
+    ex = sprintf("%s$OPT_%s %s%s ", ex, opt, c, c)
+  else
+    ex = sprintf("%s %s%s ", ex, c, c)
+  end
+  return ex
+end
+
+def setExpression(ex, opt, op)
+  if !op
+    ex = sprintf("%s$OPT_%s", ex, opt)
+    return ex
+  end
+  case op.chr
+  when "(", ")"
+    ex = setParenthesis(ex, opt, op.chr)
+  when "|", "&"
+    ex = setOrAnd(ex, opt, op.chr)
+  else
+    return nil
+  end
+  return ex
+end
+
+def parseArgs(argc, nopt, single_opts, *opts)
+  if (noOptions = getopts(single_opts, *opts)) == nil
+    printUsageAndExit()
+  end
+  if nopt
+    ex = nil
+    pos = 0
+    for o in nopt.split(/[()|&]/)
+      pos += o.length
+      ex = setExpression(ex, o, nopt[pos])
+      pos += 1
+    end
+    begin
+      if !eval(ex)
+	printUsageAndExit()
+      end
+    rescue
+      print "Format Error!! : \"" + nopt + "\"\t[parseArgs]\n"
+      exit!(-1)
+    end
+  end
+  if ARGV.length < argc
+    printUsageAndExit()
+  end
+  return noOptions
+end

Added: trunk/lib/racc/parser.rb
===================================================================
--- trunk/lib/racc/parser.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/racc/parser.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,461 @@
+#
+# parser.rb
+#
+# Copyright (c) 1999-2004 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the same terms of ruby.
+#
+# As a special exception, when this code is copied by Racc
+# into a Racc output file, you may use that output file
+# without restriction.
+#
+# $raccId: parser.rb,v 1.4 2003/11/03 13:41:47 aamine Exp $
+#
+# NOTE:
+# This file is part of the runtime library of Racc parser generator.
+# If you want to generate your own parser, you must get Racc full package.
+# Get it from: http://raa.ruby-lang.org/list.rhtml?name=racc
+#
+
+unless defined?(NotImplementedError)
+  NotImplementedError = NotImplementError
+end
+
+module Racc
+  class ParseError < StandardError; end
+end
+unless defined?(::ParseError)
+  ParseError = Racc::ParseError
+end
+
+
+module Racc
+
+  unless defined?(Racc_No_Extentions)
+    Racc_No_Extentions = false
+  end
+
+  class Parser
+
+    Racc_Runtime_Version = '1.4.4'
+    Racc_Runtime_Revision = '$raccRevision: 1.4 $'.split[1]
+
+    Racc_Runtime_Core_Version_R = '1.4.4'
+    Racc_Runtime_Core_Revision_R = '$raccRevision: 1.4 $'.split[1]
+    begin
+      require 'racc/cparse'
+    # Racc_Runtime_Core_Version_C  = (defined in extension)
+      Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2]
+      unless new.respond_to?(:_racc_do_parse_c, true)
+        raise LoadError, 'old cparse.so'
+      end
+      if Racc_No_Extentions
+        raise LoadError, 'selecting ruby version of racc runtime core'
+      end
+
+      Racc_Main_Parsing_Routine    = :_racc_do_parse_c
+      Racc_YY_Parse_Method         = :_racc_yyparse_c
+      Racc_Runtime_Core_Version    = Racc_Runtime_Core_Version_C
+      Racc_Runtime_Core_Revision   = Racc_Runtime_Core_Revision_C
+      Racc_Runtime_Type            = 'c'
+    rescue LoadError
+      Racc_Main_Parsing_Routine    = :_racc_do_parse_rb
+      Racc_YY_Parse_Method         = :_racc_yyparse_rb
+      Racc_Runtime_Core_Version    = Racc_Runtime_Core_Version_R
+      Racc_Runtime_Core_Revision   = Racc_Runtime_Core_Revision_R
+      Racc_Runtime_Type            = 'ruby'
+    end
+
+    def Parser.racc_runtime_type
+      Racc_Runtime_Type
+    end
+
+    private
+
+    def _racc_setup
+      @yydebug = false unless self.class::Racc_debug_parser
+      @yydebug = false unless defined?(@yydebug)
+      if @yydebug
+        @racc_debug_out = $stderr unless defined?(@racc_debug_out)
+        @racc_debug_out ||= $stderr
+      end
+      arg = self.class::Racc_arg
+      arg[13] = true if arg.size < 14
+      arg
+    end
+
+    def _racc_init_sysvars
+      @racc_state  = [0]
+      @racc_tstack = []
+      @racc_vstack = []
+
+      @racc_t = nil
+      @racc_val = nil
+
+      @racc_read_next = true
+
+      @racc_user_yyerror = false
+      @racc_error_status = 0
+    end
+
+    ###
+    ### do_parse
+    ###
+
+    def do_parse
+      __send__(Racc_Main_Parsing_Routine, _racc_setup(), false)
+    end
+
+    def next_token
+      raise NotImplementedError, "#{self.class}\#next_token is not defined"
+    end
+
+    def _racc_do_parse_rb( arg, in_debug )
+      action_table, action_check, action_default, action_pointer,
+      goto_table,   goto_check,   goto_default,   goto_pointer,
+      nt_base,      reduce_table, token_table,    shift_n,
+      reduce_n,     use_result,   * = arg
+
+      _racc_init_sysvars
+      tok = act = i = nil
+      nerr = 0
+
+      catch(:racc_end_parse) {
+        while true
+          if i = action_pointer[@racc_state[-1]]
+            if @racc_read_next
+              if @racc_t != 0   # not EOF
+                tok, @racc_val = next_token()
+                unless tok      # EOF
+                  @racc_t = 0
+                else
+                  @racc_t = (token_table[tok] or 1)   # error token
+                end
+                racc_read_token(@racc_t, tok, @racc_val) if @yydebug
+                @racc_read_next = false
+              end
+            end
+            i += @racc_t
+            unless i >= 0 and
+                   act = action_table[i] and
+                   action_check[i] == @racc_state[-1]
+              act = action_default[@racc_state[-1]]
+            end
+          else
+            act = action_default[@racc_state[-1]]
+          end
+          while act = _racc_evalact(act, arg)
+            ;
+          end
+        end
+      }
+    end
+
+    ###
+    ### yyparse
+    ###
+
+    def yyparse( recv, mid )
+      __send__(Racc_YY_Parse_Method, recv, mid, _racc_setup(), true)
+    end
+
+    def _racc_yyparse_rb( recv, mid, arg, c_debug )
+      action_table, action_check, action_default, action_pointer,
+      goto_table,   goto_check,   goto_default,   goto_pointer,
+      nt_base,      reduce_table, token_table,    shift_n,
+      reduce_n,     use_result,   * = arg
+
+      _racc_init_sysvars
+      tok = nil
+      act = nil
+      i = nil
+      nerr = 0
+
+      catch(:racc_end_parse) {
+        until i = action_pointer[@racc_state[-1]]
+          while act = _racc_evalact(action_default[@racc_state[-1]], arg)
+            ;
+          end
+        end
+        recv.__send__(mid) do |tok, val|
+# $stderr.puts "rd: tok=#{tok}, val=#{val}"
+          unless tok
+            @racc_t = 0
+          else
+            @racc_t = (token_table[tok] or 1)   # error token
+          end
+          @racc_val = val
+          @racc_read_next = false
+
+          i += @racc_t
+          unless i >= 0 and
+                 act = action_table[i] and
+                 action_check[i] == @racc_state[-1]
+            act = action_default[@racc_state[-1]]
+# $stderr.puts "02: act=#{act}"
+# $stderr.puts "curstate=#{@racc_state[-1]}"
+          else
+# $stderr.puts "01: act=#{act}"
+          end
+
+          while act = _racc_evalact(act, arg)
+            ;
+          end
+
+          while not (i = action_pointer[@racc_state[-1]]) or
+                not @racc_read_next or
+                @racc_t == 0   # $
+            unless i and i += @racc_t and
+                   i >= 0 and
+                   act = action_table[i] and
+                   action_check[i] == @racc_state[-1]
+              act = action_default[@racc_state[-1]]
+# $stderr.puts "04: act=#{act}"
+            else
+# $stderr.puts "03: act=#{act}"
+            end
+            while act = _racc_evalact(act, arg)
+              ;
+            end
+          end
+        end
+      }
+    end
+
+    ###
+    ### common
+    ###
+
+    def _racc_evalact( act, arg )
+# $stderr.puts "ea: act=#{act}"
+      action_table, action_check, action_default, action_pointer,
+      goto_table,   goto_check,   goto_default,   goto_pointer,
+      nt_base,      reduce_table, token_table,    shift_n,
+      reduce_n,     use_result,   * = arg
+nerr = 0   # tmp
+
+      if act > 0 and act < shift_n
+        #
+        # shift
+        #
+        if @racc_error_status > 0
+          @racc_error_status -= 1 unless @racc_t == 1   # error token
+        end
+        @racc_vstack.push @racc_val
+        @racc_state.push act
+        @racc_read_next = true
+        if @yydebug
+          @racc_tstack.push @racc_t
+          racc_shift @racc_t, @racc_tstack, @racc_vstack
+        end
+
+      elsif act < 0 and act > -reduce_n
+        #
+        # reduce
+        #
+        code = catch(:racc_jump) {
+          @racc_state.push _racc_do_reduce(arg, act)
+          false
+        }
+        if code
+          case code
+          when 1 # yyerror
+            @racc_user_yyerror = true   # user_yyerror
+            return -reduce_n
+          when 2 # yyaccept
+            return shift_n
+          else
+            raise RuntimeError, '[Racc Bug] unknown jump code'
+          end
+        end
+
+      elsif act == shift_n
+        #
+        # accept
+        #
+        racc_accept if @yydebug
+        throw :racc_end_parse, @racc_vstack[0]
+
+      elsif act == -reduce_n
+        #
+        # error
+        #
+        case @racc_error_status
+        when 0
+          unless arg[21]    # user_yyerror
+            nerr += 1
+            on_error @racc_t, @racc_val, @racc_vstack
+          end
+        when 3
+          if @racc_t == 0   # is $
+            throw :racc_end_parse, nil
+          end
+          @racc_read_next = true
+        end
+        @racc_user_yyerror = false
+        @racc_error_status = 3
+        while true
+          if i = action_pointer[@racc_state[-1]]
+            i += 1   # error token
+            if  i >= 0 and
+                (act = action_table[i]) and
+                action_check[i] == @racc_state[-1]
+              break
+            end
+          end
+
+          throw :racc_end_parse, nil if @racc_state.size <= 1
+          @racc_state.pop
+          @racc_vstack.pop
+          if @yydebug
+            @racc_tstack.pop
+            racc_e_pop @racc_state, @racc_tstack, @racc_vstack
+          end
+        end
+        return act
+
+      else
+        raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}"
+      end
+
+      racc_next_state(@racc_state[-1], @racc_state) if @yydebug
+
+      nil
+    end
+
+    def _racc_do_reduce( arg, act )
+      action_table, action_check, action_default, action_pointer,
+      goto_table,   goto_check,   goto_default,   goto_pointer,
+      nt_base,      reduce_table, token_table,    shift_n,
+      reduce_n,     use_result,   * = arg
+      state = @racc_state
+      vstack = @racc_vstack
+      tstack = @racc_tstack
+
+      i = act * -3
+      len       = reduce_table[i]
+      reduce_to = reduce_table[i+1]
+      method_id = reduce_table[i+2]
+      void_array = []
+
+      tmp_t = tstack[-len, len] if @yydebug
+      tmp_v = vstack[-len, len]
+      tstack[-len, len] = void_array if @yydebug
+      vstack[-len, len] = void_array
+      state[-len, len]  = void_array
+
+      # tstack must be updated AFTER method call
+      if use_result
+        vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
+      else
+        vstack.push __send__(method_id, tmp_v, vstack)
+      end
+      tstack.push reduce_to
+
+      racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
+
+      k1 = reduce_to - nt_base
+      if i = goto_pointer[k1]
+        i += state[-1]
+        if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
+          return curstate
+        end
+      end
+      goto_default[k1]
+    end
+
+    def on_error( t, val, vstack )
+      raise ParseError, sprintf("\nparse error on value %s (%s)",
+                                val.inspect, token_to_str(t) || '?')
+    end
+
+    def yyerror
+      throw :racc_jump, 1
+    end
+
+    def yyaccept
+      throw :racc_jump, 2
+    end
+
+    def yyerrok
+      @racc_error_status = 0
+    end
+
+    #
+    # for debugging output
+    #
+
+    def racc_read_token( t, tok, val )
+      @racc_debug_out.print 'read    '
+      @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
+      @racc_debug_out.puts val.inspect
+      @racc_debug_out.puts
+    end
+
+    def racc_shift( tok, tstack, vstack )
+      @racc_debug_out.puts "shift   #{racc_token2str tok}"
+      racc_print_stacks tstack, vstack
+      @racc_debug_out.puts
+    end
+
+    def racc_reduce( toks, sim, tstack, vstack )
+      out = @racc_debug_out
+      out.print 'reduce '
+      if toks.empty?
+        out.print ' <none>'
+      else
+        toks.each {|t| out.print ' ', racc_token2str(t) }
+      end
+      out.puts " --> #{racc_token2str(sim)}"
+          
+      racc_print_stacks tstack, vstack
+      @racc_debug_out.puts
+    end
+
+    def racc_accept
+      @racc_debug_out.puts 'accept'
+      @racc_debug_out.puts
+    end
+
+    def racc_e_pop( state, tstack, vstack )
+      @racc_debug_out.puts 'error recovering mode: pop token'
+      racc_print_states state
+      racc_print_stacks tstack, vstack
+      @racc_debug_out.puts
+    end
+
+    def racc_next_state( curstate, state )
+      @racc_debug_out.puts  "goto    #{curstate}"
+      racc_print_states state
+      @racc_debug_out.puts
+    end
+
+    def racc_print_stacks( t, v )
+      out = @racc_debug_out
+      out.print '        ['
+      t.each_index do |i|
+        out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
+      end
+      out.puts ' ]'
+    end
+
+    def racc_print_states( s )
+      out = @racc_debug_out
+      out.print '        ['
+      s.each {|st| out.print ' ', st }
+      out.puts ' ]'
+    end
+
+    def racc_token2str( tok )
+      self.class::Racc_token_to_s_table[tok] or
+          raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string"
+    end
+
+    def token_to_str( t )
+      self.class::Racc_token_to_s_table[t]
+    end
+
+  end
+
+end

Added: trunk/lib/readbytes.rb
===================================================================
--- trunk/lib/readbytes.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/readbytes.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,36 @@
+# readbytes.rb
+#
+# add IO#readbytes, which reads fixed sized data.
+# it guarantees read data size.
+
+class TruncatedDataError<IOError
+  def initialize(mesg, data)
+    @data = data
+    super(mesg)
+  end
+  attr_reader :data
+end
+
+class IO
+  def readbytes(n)
+    str = read(n)
+    if str == nil
+      raise EOFError, "End of file reached"
+    end
+    if str.size < n
+      raise TruncatedDataError.new("data truncated", str) 
+    end
+    str
+  end
+end
+
+if __FILE__ == $0
+  begin
+    loop do
+      print STDIN.readbytes(6)
+    end
+  rescue TruncatedDataError
+    p $!.data
+    raise
+  end
+end

Added: trunk/lib/rinda/.document
===================================================================
--- trunk/lib/rinda/.document	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/rinda/.document	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,3 @@
+rinda.rb
+ring.rb
+tuplespace.rb

Added: trunk/lib/rinda/rinda.rb
===================================================================
--- trunk/lib/rinda/rinda.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/rinda/rinda.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,283 @@
+require 'drb/drb'
+require 'thread'
+
+##
+# A module to implement the Linda distributed computing paradigm in Ruby.
+#
+# Rinda is part of DRb (dRuby).
+#
+# == Example(s)
+#
+# See the sample/drb/ directory in the Ruby distribution, from 1.8.2 onwards.
+#
+#--
+# TODO
+# == Introduction to Linda/rinda?
+#
+# == Why is this library separate from DRb?
+
+module Rinda
+
+  ##
+  # Rinda error base class
+
+  class RindaError < RuntimeError; end
+
+  ##
+  # Raised when a hash-based tuple has an invalid key.
+
+  class InvalidHashTupleKey < RindaError; end
+
+  ##
+  # Raised when trying to use a canceled tuple.
+
+  class RequestCanceledError < ThreadError; end
+
+  ##
+  # Raised when trying to use an expired tuple.
+
+  class RequestExpiredError < ThreadError; end
+
+  ##
+  # A tuple is the elementary object in Rinda programming.
+  # Tuples may be matched against templates if the tuple and
+  # the template are the same size.
+
+  class Tuple
+
+    ##
+    # Creates a new Tuple from +ary_or_hash+ which must be an Array or Hash.
+
+    def initialize(ary_or_hash)
+      if hash?(ary_or_hash)
+        init_with_hash(ary_or_hash)
+      else
+        init_with_ary(ary_or_hash)
+      end
+    end
+
+    ##
+    # The number of elements in the tuple.
+    
+    def size
+      @tuple.size
+    end
+
+    ##
+    # Accessor method for elements of the tuple.
+
+    def [](k)
+      @tuple[k]
+    end
+
+    ##
+    # Fetches item +k+ from the tuple.
+
+    def fetch(k)
+      @tuple.fetch(k)
+    end
+
+    ##
+    # Iterate through the tuple, yielding the index or key, and the
+    # value, thus ensuring arrays are iterated similarly to hashes.
+
+    def each # FIXME
+      if Hash === @tuple
+        @tuple.each { |k, v| yield(k, v) }
+      else
+        @tuple.each_with_index { |v, k| yield(k, v) }
+      end
+    end
+
+    ##
+    # Return the tuple itself
+    def value
+      @tuple
+    end
+
+    private
+
+    def hash?(ary_or_hash)
+      ary_or_hash.respond_to?(:keys)
+    end
+
+    ##
+    # Munges +ary+ into a valid Tuple.
+
+    def init_with_ary(ary)
+      @tuple = Array.new(ary.size)
+      @tuple.size.times do |i|
+        @tuple[i] = ary[i]
+      end
+    end
+
+    ##
+    # Ensures +hash+ is a valid Tuple.
+
+    def init_with_hash(hash)
+      @tuple = Hash.new
+      hash.each do |k, v|
+        raise InvalidHashTupleKey unless String === k
+        @tuple[k] = v
+      end
+    end
+
+  end
+
+  ##
+  # Templates are used to match tuples in Rinda.
+
+  class Template < Tuple
+
+    ##
+    # Matches this template against +tuple+.  The +tuple+ must be the same
+    # size as the template.  An element with a +nil+ value in a template acts
+    # as a wildcard, matching any value in the corresponding position in the
+    # tuple.  Elements of the template match the +tuple+ if the are #== or
+    # #===.
+    #
+    #   Template.new([:foo, 5]).match   Tuple.new([:foo, 5]) # => true
+    #   Template.new([:foo, nil]).match Tuple.new([:foo, 5]) # => true
+    #   Template.new([String]).match    Tuple.new(['hello']) # => true
+    #
+    #   Template.new([:foo]).match      Tuple.new([:foo, 5]) # => false
+    #   Template.new([:foo, 6]).match   Tuple.new([:foo, 5]) # => false
+    #   Template.new([:foo, nil]).match Tuple.new([:foo])    # => false
+    #   Template.new([:foo, 6]).match   Tuple.new([:foo])    # => false
+
+    def match(tuple)
+      return false unless tuple.respond_to?(:size)
+      return false unless tuple.respond_to?(:fetch)
+      return false unless self.size == tuple.size
+      each do |k, v|
+        begin
+          it = tuple.fetch(k)
+        rescue
+          return false
+        end
+        next if v.nil?
+        next if v == it
+        next if v === it
+        return false
+      end
+      return true
+    end
+    
+    ##
+    # Alias for #match.
+
+    def ===(tuple)
+      match(tuple)
+    end
+
+  end
+  
+  ##
+  # <i>Documentation?</i>
+
+  class DRbObjectTemplate
+
+    ##
+    # Creates a new DRbObjectTemplate that will match against +uri+ and +ref+.
+
+    def initialize(uri=nil, ref=nil)
+      @drb_uri = uri
+      @drb_ref = ref
+    end
+    
+    ##
+    # This DRbObjectTemplate matches +ro+ if the remote object's drburi and
+    # drbref are the same.  +nil+ is used as a wildcard.
+
+    def ===(ro)
+      return true if super(ro)
+      unless @drb_uri.nil?
+        return false unless (@drb_uri === ro.__drburi rescue false)
+      end
+      unless @drb_ref.nil?
+        return false unless (@drb_ref === ro.__drbref rescue false)
+      end
+      true
+    end
+
+  end
+
+  ##
+  # TupleSpaceProxy allows a remote Tuplespace to appear as local.
+
+  class TupleSpaceProxy
+
+    ##
+    # Creates a new TupleSpaceProxy to wrap +ts+.
+
+    def initialize(ts)
+      @ts = ts
+    end
+    
+    ##
+    # Adds +tuple+ to the proxied TupleSpace.  See TupleSpace#write.
+
+    def write(tuple, sec=nil)
+      @ts.write(tuple, sec)
+    end
+    
+    ##
+    # Takes +tuple+ from the proxied TupleSpace.  See TupleSpace#take.
+
+    def take(tuple, sec=nil, &block)
+      port = []
+      @ts.move(DRbObject.new(port), tuple, sec, &block)
+      port[0]
+    end
+    
+    ##
+    # Reads +tuple+ from the proxied TupleSpace.  See TupleSpace#read.
+
+    def read(tuple, sec=nil, &block)
+      @ts.read(tuple, sec, &block)
+    end
+    
+    ##
+    # Reads all tuples matching +tuple+ from the proxied TupleSpace.  See
+    # TupleSpace#read_all.
+
+    def read_all(tuple)
+      @ts.read_all(tuple)
+    end
+    
+    ##
+    # Registers for notifications of event +ev+ on the proxied TupleSpace.
+    # See TupleSpace#notify
+
+    def notify(ev, tuple, sec=nil)
+      @ts.notify(ev, tuple, sec)
+    end
+
+  end
+
+  ##
+  # An SimpleRenewer allows a TupleSpace to check if a TupleEntry is still
+  # alive.
+
+  class SimpleRenewer
+
+    include DRbUndumped
+
+    ##
+    # Creates a new SimpleRenewer that keeps an object alive for another +sec+
+    # seconds.
+
+    def initialize(sec=180)
+      @sec = sec
+    end
+
+    ##
+    # Called by the TupleSpace to check if the object is still alive.
+
+    def renew
+      @sec
+    end
+  end
+
+end
+

Added: trunk/lib/rinda/ring.rb
===================================================================
--- trunk/lib/rinda/ring.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/rinda/ring.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,271 @@
+#
+# Note: Rinda::Ring API is unstable.
+#
+require 'drb/drb'
+require 'rinda/rinda'
+require 'thread'
+
+module Rinda
+
+  ##
+  # The default port Ring discovery will use.
+
+  Ring_PORT = 7647
+
+  ##
+  # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
+  # Service location uses the following steps:
+  #
+  # 1. A RingServer begins listening on the broadcast UDP address.
+  # 2. A RingFinger sends a UDP packet containing the DRb URI where it will
+  #    listen for a reply.
+  # 3. The RingServer recieves the UDP packet and connects back to the
+  #    provided DRb URI with the DRb service.
+
+  class RingServer
+
+    include DRbUndumped
+
+    ##
+    # Advertises +ts+ on the UDP broadcast address at +port+.
+
+    def initialize(ts, port=Ring_PORT)
+      @ts = ts
+      @soc = UDPSocket.open
+      @soc.bind('', port)
+      @w_service = write_service
+      @r_service = reply_service
+    end
+
+    ##
+    # Creates a thread that picks up UDP packets and passes them to do_write
+    # for decoding.
+
+    def write_service
+      Thread.new do
+	loop do
+	  msg = @soc.recv(1024)
+	  do_write(msg)
+	end
+      end
+    end
+  
+    ##
+    # Extracts the response URI from +msg+ and adds it to TupleSpace where it
+    # will be picked up by +reply_service+ for notification.
+
+    def do_write(msg)
+      Thread.new do
+	begin
+	  tuple, sec = Marshal.load(msg)
+	  @ts.write(tuple, sec)
+	rescue
+	end
+      end
+    end
+
+    ##
+    # Creates a thread that notifies waiting clients from the TupleSpace.
+
+    def reply_service
+      Thread.new do
+	loop do
+	  do_reply
+	end
+      end
+    end
+    
+    ##
+    # Pulls lookup tuples out of the TupleSpace and sends their DRb object the
+    # address of the local TupleSpace.
+
+    def do_reply
+      tuple = @ts.take([:lookup_ring, DRbObject])
+      Thread.new { tuple[1].call(@ts) rescue nil}
+    rescue
+    end
+
+  end
+
+  ##
+  # RingFinger is used by RingServer clients to discover the RingServer's
+  # TupleSpace.  Typically, all a client needs to do is call
+  # RingFinger.primary to retrieve the remote TupleSpace, which it can then
+  # begin using.
+
+  class RingFinger
+
+    @@broadcast_list = ['<broadcast>', 'localhost']
+
+    @@finger = nil
+
+    ##
+    # Creates a singleton RingFinger and looks for a RingServer.  Returns the
+    # created RingFinger.
+
+    def self.finger
+      unless @@finger 
+	@@finger = self.new
+	  finger.lookup_ring_any
+      end
+      @@finger
+    end
+
+    ##
+    # Returns the first advertised TupleSpace.
+
+    def self.primary
+      finger.primary
+    end
+
+    ##
+    # Contains all discoverd TupleSpaces except for the primary.
+
+    def self.to_a
+      finger.to_a
+    end
+
+    ##
+    # The list of addresses where RingFinger will send query packets.
+
+    attr_accessor :broadcast_list
+
+    ##
+    # The port that RingFinger will send query packets to.
+
+    attr_accessor :port
+
+    ##
+    # Contain the first advertised TupleSpace after lookup_ring_any is called.
+
+    attr_accessor :primary
+
+    ##
+    # Creates a new RingFinger that will look for RingServers at +port+ on
+    # the addresses in +broadcast_list+.
+
+    def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
+      @broadcast_list = broadcast_list || ['localhost']
+      @port = port
+      @primary = nil
+      @rings = []
+    end
+
+    ##
+    # Contains all discovered TupleSpaces except for the primary.
+
+    def to_a
+      @rings
+    end
+
+    ##
+    # Iterates over all discovered TupleSpaces starting with the primary.
+
+    def each
+      lookup_ring_any unless @primary
+      return unless @primary
+      yield(@primary)
+      @rings.each { |x| yield(x) }
+    end
+
+    ##
+    # Looks up RingServers waiting +timeout+ seconds.  RingServers will be
+    # given +block+ as a callback, which will be called with the remote
+    # TupleSpace.
+
+    def lookup_ring(timeout=5, &block)
+      return lookup_ring_any(timeout) unless block_given?
+
+      msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
+      @broadcast_list.each do |it|
+	soc = UDPSocket.open
+	begin
+	  soc.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
+	  soc.send(msg, 0, it, @port)
+	rescue
+	  nil
+	ensure
+	  soc.close
+	end
+      end
+      sleep(timeout)
+    end
+
+    ##
+    # Returns the first found remote TupleSpace.  Any further recovered
+    # TupleSpaces can be found by calling +to_a+.
+
+    def lookup_ring_any(timeout=5)
+      queue = Queue.new
+
+      th = Thread.new do
+	self.lookup_ring(timeout) do |ts|
+	  queue.push(ts)
+	end
+	queue.push(nil)
+	while it = queue.pop
+	  @rings.push(it)
+	end
+      end
+      
+      @primary = queue.pop
+      raise('RingNotFound') if @primary.nil?
+      @primary
+    end
+
+  end
+
+  ##
+  # RingProvider uses a RingServer advertised TupleSpace as a name service.
+  # TupleSpace clients can register themselves with the remote TupleSpace and
+  # look up other provided services via the remote TupleSpace.
+  #
+  # Services are registered with a tuple of the format [:name, klass,
+  # DRbObject, description].
+
+  class RingProvider
+
+    ##
+    # Creates a RingProvider that will provide a +klass+ service running on
+    # +front+, with a +description+.  +renewer+ is optional.
+
+    def initialize(klass, front, desc, renewer = nil)
+      @tuple = [:name, klass, front, desc]
+      @renewer = renewer || Rinda::SimpleRenewer.new
+    end
+
+    ##
+    # Advertises this service on the primary remote TupleSpace.
+
+    def provide
+      ts = Rinda::RingFinger.primary
+      ts.write(@tuple, @renewer)
+    end
+
+  end
+
+end
+
+if __FILE__ == $0
+  DRb.start_service
+  case ARGV.shift
+  when 's'
+    require 'rinda/tuplespace'
+    ts = Rinda::TupleSpace.new
+    place = Rinda::RingServer.new(ts)
+    $stdin.gets
+  when 'w'
+    finger = Rinda::RingFinger.new(nil)
+    finger.lookup_ring do |ts|
+      p ts
+      ts.write([:hello, :world])
+    end
+  when 'r'
+    finger = Rinda::RingFinger.new(nil)
+    finger.lookup_ring do |ts|
+      p ts
+      p ts.take([nil, nil])
+    end
+  end
+end
+

Added: trunk/lib/rinda/tuplespace.rb
===================================================================
--- trunk/lib/rinda/tuplespace.rb	2006-02-27 02:27:32 UTC (rev 476)
+++ trunk/lib/rinda/tuplespace.rb	2006-02-27 02:53:22 UTC (rev 477)
@@ -0,0 +1,589 @@
+require 'monitor'
+require 'thread'
+require 'drb/drb'
+require 'rinda/rinda'
+
+module Rinda
+
+  ##
+  # A TupleEntry is a Tuple (i.e. a possible entry in some Tuplespace)
+  # together with expiry and cancellation data.
+
+  class TupleEntry
+
+    include DRbUndumped
+
+    attr_accessor :expires
+
+    ##
+    # Creates a TupleEntry based on +ary+ with an optional renewer or expiry
+    # time +sec+.
+    #
+    # A renewer must implement the +renew+ method which returns a Numeric,
+    # nil, or true to indicate when the tuple has expired.
+
+    def initialize(ary, sec=nil)
+      @cancel = false
+      @expires = nil
+      @tuple = make_tuple(ary)
+      @renewer = nil
+      renew(sec)
+    end
+
+    ##
+    # Marks this TupleEntry as canceled.
+
+    def cancel
+      @cancel = true
+    end
+
+    ##
+    # A TupleEntry is dead when it is canceled or expired.
+
+    def alive?
+      !canceled? && !expired?
+    end
+
+    ##
+    # Return the object which makes up the tuple itself: the Array
+    # or Hash.
+
+    def value; @tuple.value; end
+
+    ##
+    # Returns the canceled status.
+
+    def canceled?; @cancel; end
+
+    ##
+    # Has this tuple expired? (true/false).
+    #
+    # A tuple has expired when its expiry timer based on the +sec+ argument to
+    # #initialize runs out.
+
+    def expired?
+      return true unless @expires
+      return false if @expires > Time.now
+      return true if @renewer.nil?
+      renew(@renewer)
+      return true unless @expires
+      return @expires < Time.now
+    end
+
+    ##
+    # Reset the expiry time according to +sec_or_renewer+.  
+    #
+    # +nil+::    it is set to expire in the far future.
+    # +false+::  it has expired.
+    # Numeric::  it will expire in that many seconds.
+    #
+    # Otherwise the argument refers to some kind of renewer object
+    # which will reset its expiry time. 
+
+    def renew(sec_or_renewer)
+      sec, @renewer = get_renewer(sec_or_renewer)
+      @expires = make_expires(sec)
+    end
+
+    ##
+    # Returns an expiry Time based on +sec+ which can be one of:
+    # Numeric:: +sec+ seconds into the future
+    # +true+::  the expiry time is the start of 1970 (i.e. expired)
+    # +nil+::   it is  Tue Jan 19 03:14:07 GMT Standard Time 2038 (i.e. when
+    #           UNIX clocks will die)
+
+    def make_expires(sec=nil)
+      case sec
+      when Numeric
+        Time.now + sec
+      when true
+        Time.at(1)
+      when nil
+        Time.at(2**31-1)
+      end
+    end
+
+    ##
+    # Retrieves +key+ from the tuple.
+
+    def [](key)
+      @tuple[key]
+    end
+
+    ##
+    # Fetches +key+ from the tuple.
+
+    def fetch(key)
+      @tuple.fetch(key)
+    end
+
+    ##
+    # The size of the tuple.
+
+    def size
+      @tuple.size
+    end
+
+    ##
+    # Creates a Rinda::Tuple for +ary+.
+
+    def make_tuple(ary)
+      Rinda::Tuple.new(ary)
+    end
+
+    private
+
+    ##
+    # Returns a valid argument to make_expires and the renewer or nil.
+    #
+    # Given +true+, +nil+, or Numeric, returns that value and +nil+ (no actual
+    # renewer).  Otherwise it returns an expiry value from calling +it.renew+
+    # and the renewer.
+
+    def get_renewer(it)
+      case it
+      when Numeric, true, nil
+        return it, nil
+      else
+        begin
+          return it.renew, it
+        rescue Exception
+          return it, nil
+        end
+      end
+    end
+
+  end
+
+  ##
+  # A TemplateEntry is a Template together with expiry and cancellation data.
+
+  class TemplateEntry < TupleEntry
+    ##
+    # Matches this TemplateEntry against +tuple+.  See Template#match for
+    # details on how a Template matches a Tuple.
+
+    def match(tuple)
+      @tuple.match(tuple)
+    end
+    
+    alias === match
+
+    def make_tuple(ary) # :nodoc:
+      Rinda::Template.new(ary)
+    end
+
+  end
+
+  ##
+  # <i>Documentation?</i>
+
+  class WaitTemplateEntry < TemplateEntry
+
+    attr_reader :found
+
+    def initialize(place, ary, expires=nil)
+      super(ary, expires)
+      @place = place
+      @cond = place.new_cond
+      @found = nil
+    end
+
+    def cancel
+      super
+      signal
+    end
+
+    def wait
+      @cond.wait
+    end
+
+    def read(tuple)
+      @found = tuple
+      signal
+    end
+
+    def signal
+      @place.synchronize do
+        @cond.signal
+      end
+    end
+
+  end
+
+  ##
+  # A NotifyTemplateEntry is returned by TupleSpace#notify and is notified of
+  # TupleSpace changes.  You may receive either your subscribed event or the
+  # 'close' event when iterating over notifications.
+  #
+  # See TupleSpace#notify_event for valid notification types.
+  #
+  # == Example
+  #
+  #   ts = Rinda::TupleSpace.new
+  #   observer = ts.notify 'write', [nil]
+  #   
+  #   Thread.start do
+  #     observer.each { |t| p t }
+  #   end
+  #   
+  #   3.times { |i| ts.write [i] }
+  #
+  # Outputs:
+  #
+  #   ['write', [0]]
+  #   ['write', [1]]
+  #   ['write', [2]]
+
+  class NotifyTemplateEntry < TemplateEntry
+
+    ##
+    # Creates a new NotifyTemplateEntry that watches +place+ for +event+s that
+    # match +tuple+.
+
+    def initialize(place, event, tuple, expires=nil)
+      ary = [event, Rinda::Template.new(tuple)]
+      super(ary, expires)
+      @queue = Queue.new
+      @done = false
+    end
+
+    ##
+    # Called by TupleSpace to notify this NotifyTemplateEntry of a new event.
+
+    def notify(ev)
+      @queue.push(ev)
+    end
+
+    ##
+    # Retrieves a notification.  Raises RequestExpiredError when this
+    # NotifyTemplateEntry expires.
+
+    def pop
+      raise RequestExpiredError if @done
+      it = @queue.pop
+      @done = true if it[0] == 'close'
+      return it
+    end
+
+    ##
+    # Yields event/tuple pairs until this NotifyTemplateEntry expires.
+
+    def each # :yields: event, tuple
+      while !@done
+        it = pop
+        yield(it)
+      end
+    rescue 
+    ensure
+      cancel
+    end
+
+  end
+
+  ##
+  # TupleBag is an unordered collection of tuples. It is the basis
+  # of Tuplespace.
+
+  class TupleBag
+
+    def initialize # :nodoc:
+      @hash = {}
+    end
+
+    ##
+    # +true+ if the TupleBag to see if it has any expired entries.
+
+    def has_expires?
+      @hash.each do |k, v|
+        v.each do |tuple|
+          return true if tuple.expires
+        end
+      end
+      false
+    end
+
+    ##
+    # Add +ary+ to the TupleBag.
+
+    def push(ary)
+      size = ary.size
+      @hash[size] ||= []
+      @hash[size].push(ary)
+    end
+
+    ##
+    # Removes +ary+ from the TupleBag.
+
+    def delete(ary)
+      size = ary.size
+      @hash.fetch(size, []).delete(ary)
+    end
+
+    ##
+    # Finds all live tuples that match +template+.
+
+    def find_all(template)
+      @hash.fetch(template.size, []).find_all do |tuple|
+        tuple.alive? && template.match(tuple)
+      end
+    end
+
+    ##
+    # Finds a live tuple that matches +template+.
+
+    def find(template)
+      @hash.fetch(template.size, []).find do |tuple|
+        tuple.alive? && template.match(tuple)
+      end
+    end
+
+    ##
+    # Finds all tuples in the TupleBag which when treated as templates, match
+    # +tuple+ and are alive.
+
+    def find_all_template(tuple)
+      @hash.fetch(tuple.size, []).find_all do |template|
+        template.alive? && template.match(tuple)
+      end
+    end
+
+    ##
+    # Delete tuples which dead tuples from the TupleBag, returning the deleted
+    # tuples.
+
+    def delete_unless_alive
+      deleted = []
+      @hash.keys.each do |size|
+        ary = []
+        @hash[size].each do |tuple|
+          if tuple.alive?
+            ary.push(tuple)
+          else
+            deleted.push(tuple)
+          end
+        end
+        @hash[size] = ary
+      end
+      deleted
+    end
+
+  end
+
+  ##
+  # The Tuplespace manages access to the tuples it contains,
+  # ensuring mutual exclusion requirements are met.
+  #
+  # The +sec+ option for the write, take, move, read and notify methods may
+  # either be a number of seconds or a Renewer object.
+
+  class TupleSpace
+
+    include DRbUndumped
+    include MonitorMixin
+
+    ##
+    # Creates a new TupleSpace.  +period+ is used to control how often to look
+    # for dead tuples after modifications to the TupleSpace.
+    #
+    # If no dead tuples are found +period+ seconds after the last
+    # modification, the TupleSpace will stop looking for dead tuples.
+
+    def initialize(period=60)
+      super()
+      @bag = TupleBag.new
+      @read_waiter = TupleBag.new
+      @take_waiter = TupleBag.new
+      @notify_waiter = TupleBag.new
+      @period = period
+      @keeper = nil
+    end
+
+    ##
+    # Adds +tuple+
+
+    def write(tuple, sec=nil)
+      entry = TupleEntry.new(tuple, sec)
+      start_keeper
+      synchronize do
+        if entry.expired?
+          @read_waiter.find_all_template(entry).each do |template|
+            template.read(tuple)
+          end
+          notify_event('write', entry.value)
+          notify_event('delete', entry.value)
+        else
+          @bag.push(entry)
+          @read_waiter.find_all_template(entry).each do |template|
+            template.read(tuple)
+          end
+          @take_waiter.find_all_template(entry).each do |template|
+            template.signal
+          end
+          notify_event('write', entry.value)
+        end
+      end
+      entry
+    end
+
+    ##
+    # Removes +tuple+
+
+    def take(tuple, sec=nil, &block)
+      move(nil, tuple, sec, &block)
+    end
+
+    ##
+    # Moves +tuple+ to +port+.
+
+    def move(port, tuple, sec=nil)
+      template = WaitTemplateEntry.new(self, tuple, sec)
+      yield(template) if block_given?
+      start_keeper
+      synchronize do
+        entry = @bag.find(template)
+        if entry
+          port.push(entry.value) if port
+          @bag.delete(entry)
+          notify_event('take', entry.value)
+          return entry.value
+        end
+        raise RequestExpiredError if template.expired?
+
+        begin
+          @take_waiter.push(template)
+          while true
+            raise RequestCanceledError if template.canceled?
+            raise RequestExpiredError if template.expired?
+            entry = @bag.find(template)
+            if entry
+              port.push(entry.value) if port
+              @bag.delete(entry)
+              notify_event('take', entry.value)
+              return entry.value
+            end
+            template.wait
+          end
+        ensure
+          @take_waiter.delete(template)
+        end
+      end
+    end
+
+    ##
+    # Reads +tuple+, but does not remove it.
+
+    def read(tuple, sec=nil)
+      template = WaitTemplateEntry.new(self, tuple, sec)
+      yield(template) if block_given?
+      start_keeper
+      synchronize do
+        entry = @bag.find(template)
+        return entry.value if entry
+        raise RequestExpiredError if template.expired?
+
+        begin
+          @read_waiter.push(template)
+          template.wait
+          raise RequestCanceledError if template.canceled?
+          raise RequestExpiredError if template.expired?
+          return template.found
+        ensure
+          @read_waiter.delete(template)
+        end
+      end
+    end
+
+    ##
+    # Returns all tuples matching +tuple+.  Does not remove the found tuples.
+
+    def read_all(tuple)
+      template = WaitTemplateEntry.new(self, tuple, nil)
+      synchronize do
+        entry = @bag.find_all(template)
+        entry.collect do |e|
+          e.value
+        end
+      end
+    end
+
+    ##
+    # Registers for notifications of +event+.  Returns a NotifyTemplateEntry.
+    # See NotifyTemplateEntry for examples of how to listen for notifications.
+    #
+    # +event+ can be:
+    # 'write'::  A tuple was added
+    # 'take'::   A tuple was taken or moved
+    # 'delete':: A tuple was lost after being overwritten or expiring
+    #
+    # The TupleSpace will also notify you of the 'close' event when the
+    # NotifyTemplateEntry has expired.
+
+    def notify(event, tuple, sec=nil)
+      template = NotifyTemplateEntry.new(self, event, tuple, sec)
+      synchronize do
+        @notify_waiter.push(template)
+      end
+      template
+    end
+
+    private
+
+    ##
+    # Removes dead tuples.
+
+    def keep_clean
+      synchronize do
+        @read_waiter.delete_unless_alive.each do |e|
+          e.signal
+        end
+        @take_waiter.delete_unless_alive.each do |e|
+          e.signal
+        end
+        @notify_waiter.delete_unless_alive.each do |e|
+          e.notify(['close'])
+        end
+        @bag.delete_unless_alive.each do |e|
+          notify_event('delete', e.value)
+        end
+      end
+    end
+
+    ##
+    # Notifies all registered listeners for +event+ of a status change of
+    # +tuple+.
+
+    def notify_event(event, tuple)
+      ev = [event, tuple]
+      @notify_waiter.find_all_template(ev).each do |template|
+        template.notify(ev)
+      end
+    end
+
+    ##
+    # Creates a thread that scans the tuplespace for expired tuples.
+
+    def start_keeper
+      return if @keeper && @keeper.alive?
+      @keeper = Thread.new do
+        while need_keeper?
+          keep_clean
+          sleep(@period)
+        end
+      end
+    end
+
+    ##
+    # Checks the tuplespace to see if it needs cleaning.
+
+    def need_keeper?
+      return true if @bag.has_expires?
+      return true if @read_waiter.has_expires?
+      return true if @take_waiter.has_expires?
+      return true if @notify_waiter.has_expires?
+    end
+
+  end
+
+end
+


-- 
ML: yarv-diff quickml.atdot.net
Info: http://www.atdot.net/~ko1/quickml

[前][次][番号順一覧][スレッド一覧][生データ]