diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/.document | 2 | ||||
-rw-r--r-- | lib/irb.rb | 2 | ||||
-rw-r--r-- | lib/matrix.rb | 2 | ||||
-rw-r--r-- | lib/pstore.rb | 222 | ||||
-rw-r--r-- | lib/rational.rb | 291 |
5 files changed, 445 insertions, 74 deletions
diff --git a/lib/.document b/lib/.document index 8abcd21d9..98dadba57 100644 --- a/lib/.document +++ b/lib/.document @@ -26,6 +26,8 @@ net observer.rb optionparser.rb pathname.rb +pstore.rb +rational.rb resolv.rb set.rb shellwords.rb diff --git a/lib/irb.rb b/lib/irb.rb index 5cacbf859..1fb4397e6 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -119,7 +119,7 @@ module IRB end if @context.auto_indent_mode unless ltype - ind = prompt(@context.prompt_i, ltype, indent, line_no).size + + ind = prompt(@context.prompt_i, ltype, indent, line_no)[/.*\z/].size + indent * 2 - p.size ind += 2 if continue @context.io.prompt = p + " " * ind if ind > 0 diff --git a/lib/matrix.rb b/lib/matrix.rb index c62acdf9a..8058addb9 100644 --- a/lib/matrix.rb +++ b/lib/matrix.rb @@ -246,7 +246,7 @@ class Matrix # use to general users. # def initialize(init_method, *argv) - self.send(init_method, *argv) + self.funcall(init_method, *argv) end def init_rows(rows, copy) diff --git a/lib/pstore.rb b/lib/pstore.rb index 51cef6e13..3f60b593f 100644 --- a/lib/pstore.rb +++ b/lib/pstore.rb @@ -1,24 +1,91 @@ +# = PStore -- Transactional File Storage for Ruby Objects # -# How to use: +# pstore.rb - +# by unknown +# documentation by Kev Jackson and James Edward Gray II # -# db = PStore.new("/tmp/foo") -# db.transaction do -# p db.roots -# ary = db["root"] = [1,2,3,4] -# ary[0] = [1,1.5] -# end +# See PStore for documentation. -# db.transaction do -# p db["root"] -# end require "fileutils" require "digest/md5" +# +# PStore implements a file based persistance mechanism based on a Hash. User +# code can store hierarchies of Ruby objects (values) into the data store file +# by name (keys). An object hierarchy may be just a single object. User code +# may later read values back from the data store or even update data, as needed. +# +# The transactional behavior ensures that any changes succeed or fail together. +# This can be used to ensure that the data store is not left in a transitory +# state, where some values were upated but others were not. +# +# Behind the scenes, Ruby objects are stored to the data store file with +# Marshal. That carries the usual limitations. Proc objects cannot be +# marshalled, for example. +# +# == Usage example: +# +# require "pstore" +# +# # a mock wiki object... +# class WikiPage +# def initialize( page_name, author, contents ) +# @page_name = page_name +# @revisions = Array.new +# +# add_revision(author, contents) +# end +# +# attr_reader :page_name +# +# def add_revision( author, contents ) +# @revisions << { :created => Time.now, +# :author => author, +# :contents => contents } +# end +# +# def wiki_page_references +# [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/) +# end +# +# # ... +# end +# +# # create a new page... +# home_page = WikiPage.new( "HomePage", "James Edward Gray II", +# "A page about the JoysOfDocumentation..." ) +# +# # then we want to update page data and the index together, or not at all... +# wiki = PStore.new("wiki_pages.pstore") +# wiki.transaction do # begin transaction; do all of this or none of it +# # store page... +# wiki[home_page.page_name] = home_page +# # ensure that an index has been created... +# wiki[:wiki_index] ||= Array.new +# # update wiki index... +# wiki[:wiki_index].push(*home_page.wiki_page_references) +# end # commit changes to wiki data store file +# +# ### Some time later... ### +# +# # read wiki data... +# wiki.transaction(true) do # begin read-only transaction, no changes allowed +# wiki.roots.each do |data_root_name| +# p data_root_name +# p wiki[data_root_name] +# end +# end +# class PStore + # The error type thrown by all PStore methods. class Error < StandardError end + # + # To construct a PStore object, pass in the _file_ path where you would like + # the data to be stored. + # def initialize(file) dir = File::dirname(file) unless File::directory? dir @@ -32,19 +99,41 @@ class PStore @abort = false end + # Raises PStore::Error if the calling code is not in a PStore#transaction. def in_transaction raise PStore::Error, "not in transaction" unless @transaction end + # + # Raises PStore::Error if the calling code is not in a PStore#transaction or + # if the code is in a read-only PStore#transaction. + # def in_transaction_wr() in_transaction() raise PStore::Error, "in read-only transaction" if @rdonly end private :in_transaction, :in_transaction_wr + # + # Retrieves a value from the PStore file data, by _name_. The hierarchy of + # Ruby objects stored under that root _name_ will be returned. + # + # *WARNING*: This method is only valid in a PStore#transaction. It will + # raise PStore::Error if called at any other time. + # def [](name) in_transaction @table[name] end + # + # This method is just like PStore#[], save that you may also provide a + # _default_ value for the object. In the event the specified _name_ is not + # found in the data store, your _default_ will be returned instead. If you do + # not specify a default, PStore::Error will be raised if the object is not + # found. + # + # *WARNING*: This method is only valid in a PStore#transaction. It will + # raise PStore::Error if called at any other time. + # def fetch(name, default=PStore::Error) unless @table.key? name if default==PStore::Error @@ -55,39 +144,138 @@ class PStore end self[name] end + # + # Stores an individual Ruby object or a hierarchy of Ruby objects in the data + # store file under the root _name_. Assigning to a _name_ already in the data + # store clobbers the old data. + # + # == Example: + # + # require "pstore" + # + # store = PStore.new("data_file.pstore") + # store.transaction do # begin transaction + # # load some data into the store... + # store[:single_object] = "My data..." + # store[:obj_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"], + # "James Gray" => ["erb.rb", "pstore.rb"] } + # end # commit changes to data store file + # + # *WARNING*: This method is only valid in a PStore#transaction and it cannot + # be read-only. It will raise PStore::Error if called at any other time. + # def []=(name, value) in_transaction_wr() @table[name] = value end + # + # Removes an object hierarchy from the data store, by _name_. + # + # *WARNING*: This method is only valid in a PStore#transaction and it cannot + # be read-only. It will raise PStore::Error if called at any other time. + # def delete(name) in_transaction_wr() @table.delete name end + # + # Returns the names of all object hierarchies currently in the store. + # + # *WARNING*: This method is only valid in a PStore#transaction. It will + # raise PStore::Error if called at any other time. + # def roots in_transaction @table.keys end + # + # Returns true if the supplied _name_ is currently in the data store. + # + # *WARNING*: This method is only valid in a PStore#transaction. It will + # raise PStore::Error if called at any other time. + # def root?(name) in_transaction @table.key? name end + # Returns the path to the data store file. def path @filename end + # + # Ends the current PStore#transaction, committing any changes to the data + # store immediately. + # + # == Example: + # + # require "pstore" + # + # store = PStore.new("data_file.pstore") + # store.transaction do # begin transaction + # # load some data into the store... + # store[:one] = 1 + # store[:two] = 2 + # + # store.commit # end transaction here, committing changes + # + # store[:three] = 3 # this change is never reached + # end + # + # *WARNING*: This method is only valid in a PStore#transaction. It will + # raise PStore::Error if called at any other time. + # def commit in_transaction @abort = false throw :pstore_abort_transaction end + # + # Ends the current PStore#transaction, discarding any changes to the data + # store. + # + # == Example: + # + # require "pstore" + # + # store = PStore.new("data_file.pstore") + # store.transaction do # begin transaction + # store[:one] = 1 # this change is not applied, see below... + # store[:two] = 2 # this change is not applied, see below... + # + # store.abort # end transaction here, discard all changes + # + # store[:three] = 3 # this change is never reached + # end + # + # *WARNING*: This method is only valid in a PStore#transaction. It will + # raise PStore::Error if called at any other time. + # def abort in_transaction @abort = true throw :pstore_abort_transaction end - def transaction(read_only=false) + # + # Opens a new transaction for the data store. Code executed inside a block + # passed to this method may read and write data to and from the data store + # file. + # + # At the end of the block, changes are committed to the data store + # automatically. You may exit the transaction early with a call to either + # PStore#commit or PStore#abort. See those methods for details about how + # changes are handled. Raising an uncaught Exception in the block is + # equivalent to calling PStore#abort. + # + # If _read_only_ is set to +true+, you will only be allowed to read from the + # data store during the transaction and any attempts to change the data will + # raise a PStore::Error. + # + # Note that PStore does not support nested transactions. + # + def transaction(read_only=false) # :yields: pstore raise PStore::Error, "nested transaction" if @transaction begin @rdonly = read_only @@ -155,19 +343,23 @@ class PStore value end - def dump(table) + # This method is just a wrapped around Marshal.dump. + def dump(table) # :nodoc: Marshal::dump(table) end - def load(content) + # This method is just a wrapped around Marshal.load. + def load(content) # :nodoc: Marshal::load(content) end - def load_file(file) + # This method is just a wrapped around Marshal.load. + def load_file(file) # :nodoc: Marshal::load(file) end private + # Commits changes to the data store file. def commit_new(f) f.truncate(0) f.rewind @@ -180,6 +372,8 @@ class PStore end end +# :enddoc: + if __FILE__ == $0 db = PStore.new("/tmp/foo") db.transaction do diff --git a/lib/rational.rb b/lib/rational.rb index 224100485..fd5bc95a3 100644 --- a/lib/rational.rb +++ b/lib/rational.rb @@ -1,41 +1,32 @@ # -# rational.rb - -# $Release Version: 0.5 $ -# $Revision: 1.7 $ -# $Date: 1999/08/24 12:49:28 $ -# by Keiju ISHITSUKA(SHL Japan Inc.) +# rational.rb - +# $Release Version: 0.5 $ +# $Revision: 1.7 $ +# $Date: 1999/08/24 12:49:28 $ +# by Keiju ISHITSUKA(SHL Japan Inc.) # -# -- -# Usage: -# class Rational < Numeric -# (include Comparable) +# Documentation by Kevin Jackson and Gavin Sinclair. +# +# When you <tt>require 'rational'</tt>, all interactions between numbers +# potentially return a rational result. For example: # -# Rational(a, b) --> a/b +# 1.quo(2) # -> 0.5 +# require 'rational' +# 1.quo(2) # -> Rational(1,2) +# +# See Rational for full documentation. # -# Rational::+ -# Rational::- -# Rational::* -# Rational::/ -# Rational::** -# Rational::% -# Rational::divmod -# Rational::abs -# Rational::<=> -# Rational::to_i -# Rational::to_f -# Rational::to_s + # -# Integer::gcd -# Integer::lcm -# Integer::gcdlcm -# Integer::to_r +# Creates a Rational number (i.e. a fraction). +a+ and +b+ should be Integers: +# +# Rational(1,3) # -> 1/3 # -# Fixnum::** -# Fixnum::quo -# Bignum::** -# Bignum::quo +# Note: trying to construct a Rational with floating point or real values +# produces errors: +# +# Rational(1.1, 2.3) # -> NoMethodError # - def Rational(a, b = 1) if a.kind_of?(Rational) && b == 1 a @@ -43,10 +34,39 @@ def Rational(a, b = 1) Rational.reduce(a, b) end end - + +# +# Rational implements a rational class for numbers. +# +# <em>A rational number is a number that can be expressed as a fraction p/q +# where p and q are integers and q != 0. A rational number p/q is said to have +# numerator p and denominator q. Numbers that are not rational are called +# irrational numbers.</em> (http://mathworld.wolfram.com/RationalNumber.html) +# +# To create a Rational Number: +# Rational(a,b) # -> a/b +# Rational.new!(a,b) # -> a/b +# +# Examples: +# Rational(5,6) # -> 5/6 +# Rational(5) # -> 5/1 +# +# Rational numbers are reduced to their lowest terms: +# Rational(6,10) # -> 3/5 +# +# But not if you use the unusual method "new!": +# Rational.new!(6,10) # -> 6/10 +# +# Division by zero is obviously not allowed: +# Rational(3,0) # -> ZeroDivisionError +# class Rational < Numeric @RCS_ID='-$Id: rational.rb,v 1.7 1999/08/24 12:49:28 keiju Exp keiju $-' + # + # Reduces the given numerator and denominator to their lowest terms. Use + # Rational() instead. + # def Rational.reduce(num, den = 1) raise ZeroDivisionError, "denominator is zero" if den == 0 @@ -63,13 +83,21 @@ class Rational < Numeric new!(num, den) end end - + + # + # Implements the constructor. This method does not reduce to lowest terms or + # check for division by zero. Therefore #Rational() should be preferred in + # normal use. + # def Rational.new!(num, den = 1) new(num, den) end private_class_method :new + # + # This method is actually private. + # def initialize(num, den) if den < 0 num = -num @@ -83,7 +111,15 @@ class Rational < Numeric @denominator = den.to_i end end - + + # + # Returns the addition of this value and +a+. + # + # Examples: + # r = Rational(3,4) # -> Rational(3,4) + # r + 1 # -> Rational(7,4) + # r + 0.5 # -> 1.25 + # def + (a) if a.kind_of?(Rational) num = @numerator * a.denominator @@ -98,7 +134,16 @@ class Rational < Numeric x + y end end - + + # + # Returns the difference of this value and +a+. + # subtracted. + # + # Examples: + # r = Rational(3,4) # -> Rational(3,4) + # r - 1 # -> Rational(-1,4) + # r - 0.5 # -> 0.25 + # def - (a) if a.kind_of?(Rational) num = @numerator * a.denominator @@ -113,7 +158,17 @@ class Rational < Numeric x - y end end - + + # + # Returns the product of this value and +a+. + # + # Examples: + # r = Rational(3,4) # -> Rational(3,4) + # r * 2 # -> Rational(3,2) + # r * 4 # -> Rational(3,1) + # r * 0.5 # -> 0.375 + # r * Rational(1,2) # -> Rational(3,8) + # def * (a) if a.kind_of?(Rational) num = @numerator * a.numerator @@ -128,7 +183,14 @@ class Rational < Numeric x * y end end - + + # + # Returns the quotient of this value and +a+. + # r = Rational(3,4) # -> Rational(3,4) + # r / 2 # -> Rational(3,8) + # r / 2.0 # -> 0.375 + # r / Rational(1,2) # -> Rational(3,2) + # def / (a) if a.kind_of?(Rational) num = @numerator * a.denominator @@ -144,7 +206,16 @@ class Rational < Numeric x / y end end - + + # + # Returns this value raised to the given power. + # + # Examples: + # r = Rational(3,4) # -> Rational(3,4) + # r ** 2 # -> Rational(9,16) + # r ** 2.0 # -> 0.5625 + # r ** Rational(1,2) # -> 0.866025403784439 + # def ** (other) if other.kind_of?(Rational) Float(self) ** other @@ -167,17 +238,37 @@ class Rational < Numeric x ** y end end - + + # + # Returns the remainder when this value is divided by +other+. + # + # Examples: + # r = Rational(7,4) # -> Rational(7,4) + # r % Rational(1,2) # -> Rational(1,4) + # r % 1 # -> Rational(3,4) + # r % Rational(1,7) # -> Rational(1,28) + # r % 0.26 # -> 0.19 + # def % (other) value = (self / other).to_i return self - other * value end - + + # + # Returns the quotient _and_ remainder. + # + # Examples: + # r = Rational(7,4) # -> Rational(7,4) + # r.divmod Rational(1,2) # -> [3, Rational(1,4)] + # def divmod(other) value = (self / other).to_i return value, self - other * value end - + + # + # Returns the absolute value. + # def abs if @numerator > 0 Rational.new!(@numerator, @denominator) @@ -186,6 +277,15 @@ class Rational < Numeric end end + # + # Returns +true+ iff this value is numerically equal to +other+. + # + # But beware: + # Rational(1,2) == Rational(4,8) # -> true + # Rational(1,2) == Rational.new!(4,8) # -> false + # + # Don't use Rational.new! + # def == (other) if other.kind_of?(Rational) @numerator == other.numerator and @denominator == other.denominator @@ -198,6 +298,9 @@ class Rational < Numeric end end + # + # Standard comparison operator. + # def <=> (other) if other.kind_of?(Rational) num = @numerator * other.denominator @@ -232,14 +335,35 @@ class Rational < Numeric end end + # + # Converts the rational to an Integer. Not the _nearest_ integer, the + # truncated integer. Study the following example carefully: + # Rational(+7,4).to_i # -> 1 + # Rational(-7,4).to_i # -> -2 + # (-1.75).to_i # -> -1 + # + # In other words: + # Rational(-7,4) == -1.75 # -> true + # Rational(-7,4).to_i == (-1.75).to_i # false + # def to_i Integer(@numerator.div(@denominator)) end - + + # + # Converts the rational to a Float. + # def to_f @numerator.to_f/@denominator.to_f end - + + # + # Returns a string representation of the rational number. + # + # Example: + # Rational(3,4).to_s # "3/4" + # Rational(8).to_s # "8" + # def to_s if @denominator == 1 @numerator.to_s @@ -247,38 +371,69 @@ class Rational < Numeric @numerator.to_s+"/"+@denominator.to_s end end - + + # + # Returns +self+. + # def to_r self end - + + # + # Returns a reconstructable string representation: + # + # Rational(5,8).inspect # -> "Rational(5, 8)" + # def inspect sprintf("Rational(%s, %s)", @numerator.inspect, @denominator.inspect) end - + + # + # Returns a hash code for the object. + # def hash @numerator.hash ^ @denominator.hash end - + attr :numerator attr :denominator - + private :initialize end class Integer + # + # In an integer, the value _is_ the numerator of its rational equivalent. + # Therefore, this method returns +self+. + # def numerator self end - + + # + # In an integer, the denominator is 1. Therefore, this method returns 1. + # def denominator 1 end - + + # + # Returns a Rational representation of this integer. + # def to_r Rational(self, 1) end - + + # + # Returns the <em>greatest common denominator</em> of the two numbers (+self+ + # and +n+). + # + # Examples: + # 72.gcd 168 # -> 24 + # 19.gcd 36 # -> 1 + # + # The result is positive, no matter the sign of the arguments. + # def gcd(n) m = self.abs n = n.abs @@ -298,13 +453,13 @@ class Integer end m << b end - + def gcd2(int) a = self.abs b = int.abs - + a, b = b, a if a < b - + while b != 0 void, a = a.divmod(b) a, b = b, a @@ -312,6 +467,14 @@ class Integer return a end + # + # Returns the <em>lowest common multiple</em> (LCM) of the two arguments + # (+self+ and +other+). + # + # Examples: + # 6.lcm 7 # -> 42 + # 6.lcm 9 # -> 18 + # def lcm(other) if self.zero? or other.zero? 0 @@ -320,6 +483,14 @@ class Integer end end + # + # Returns the GCD _and_ the LCM (see #gcd and #lcm) of the two arguments + # (+self+ and +other+). This is more efficient than calculating them + # separately. + # + # Example: + # 6.gcdlcm 9 # -> [3, 18] + # def gcdlcm(other) gcd = self.gcd(other) if self.zero? or other.zero? @@ -332,11 +503,13 @@ end class Fixnum undef quo + # If Rational is defined, returns a Rational number instead of a Fixnum. def quo(other) Rational.new!(self,1) / other end alias rdiv quo - + + # Returns a Rational number if the result is in fact rational (i.e. +other+ < 0). def rpower (other) if other >= 0 self.power!(other) @@ -346,7 +519,7 @@ class Fixnum end unless defined? 1.power! - alias power! ** + alias power! ** alias ** rpower end end @@ -357,11 +530,13 @@ class Bignum end undef quo + # If Rational is defined, returns a Rational number instead of a Bignum. def quo(other) Rational.new!(self,1) / other end alias rdiv quo - + + # Returns a Rational number if the result is in fact rational (i.e. +other+ < 0). def rpower (other) if other >= 0 self.power!(other) @@ -369,7 +544,7 @@ class Bignum Rational.new!(self, 1)**other end end - + unless defined? Complex alias ** rpower end |