multimethod 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ChangeLog CHANGED
@@ -1,3 +1,8 @@
1
+ 2006-11-29 Kurt A. Stephens <kurt@umleta.com>
2
+
3
+ * Major cleanup and documentation
4
+ * Fixed default parameter scoring.
5
+
1
6
  2006-11-18 Kurt A. Stephens <kurt@umleta.com>
2
7
 
3
8
  * Initial release
data/README.txt CHANGED
@@ -15,7 +15,7 @@ gem install multimethod
15
15
  The RubyForge package multimethod
16
16
  implements multi-method dispatching on argument types.
17
17
 
18
- See also: ?????http://umleta.com/node/?????
18
+ See also: http://umleta.com/node/7
19
19
 
20
20
  == Home page
21
21
 
data/Rakefile CHANGED
@@ -30,7 +30,7 @@ def get_release_notes(relfile = "Releases.txt")
30
30
 
31
31
  File.open(relfile) do |f|
32
32
  while ! f.eof? && line = f.readline
33
- if md = /^== Release ([\d\.]+)/i.match(line)
33
+ if md = /^=+ Release ([\d\.]+)/i.match(line)
34
34
  release = md[1]
35
35
  notes << line
36
36
  break
@@ -38,14 +38,14 @@ def get_release_notes(relfile = "Releases.txt")
38
38
  end
39
39
 
40
40
  while ! f.eof? && line = f.readline
41
- if md = /^== Release ([\d\.]+)/i.match(line)
41
+ if md = /^=+ Release ([\d\.]+)/i.match(line)
42
42
  break
43
43
  end
44
44
  notes << line
45
45
  end
46
46
  end
47
47
 
48
- $stderr.puts "Release #{release.inspect}"
48
+ # $stderr.puts "Release #{release.inspect}"
49
49
  [ release, notes.join('') ]
50
50
  end
51
51
 
@@ -83,10 +83,10 @@ version_rb = "lib/#{PKG_NAME}/#{PKG_NAME}_version.rb"
83
83
  task :update_version do
84
84
  announce "Updating #{PKG_Name} version to #{PKG_VERSION}: #{version_rb}"
85
85
  open(version_rb, "w") do |f|
86
+ f.puts "module #{PKG_Name}"
86
87
  f.puts "# DO NOT EDIT"
87
88
  f.puts "# This file is auto-generated by build scripts."
88
89
  f.puts "# See: rake update_version"
89
- f.puts "module #{PKG_Name}"
90
90
  f.puts " #{PKG_Name}Version = '#{PKG_VERSION}'"
91
91
  f.puts "end"
92
92
  end
@@ -1,14 +1,21 @@
1
1
  = Multimethod Release History
2
2
 
3
+ == Release 0.2.0: 2006/11/29
4
+
5
+ * Fixed default parameter scoring.
6
+ * Added more documentation.
7
+ * TODO:
8
+ * Need ambigious method test cases.
9
+
3
10
  == Release 0.1.0: 2006/11/24
4
11
 
5
12
  * Multimethods can be added and removed.
6
13
  * Better internal design: signatures vs methods.
7
14
  * TODO
8
- * Fix how default parameters are scored.
15
+ * Fix how default parameters are scored.
9
16
 
10
17
  == Release 0.0.1: 2006/11/18
11
18
 
12
19
  * Initial Release
13
20
  * TODO
14
- * Fix how default parameters are scored.
21
+ * Fix how default parameters are scored.
@@ -1,3 +1,68 @@
1
+ # == Introduction
2
+ #
3
+ # The Multimethod package implements dispatch of messages to
4
+ # multiple methods based on argument types.
5
+ #
6
+ # Variadic methods and default values are supported.
7
+ #
8
+ # Methods can be added and removed at run-time.
9
+ #
10
+ # == Examples
11
+ #
12
+ # require 'multimethod'
13
+ #
14
+ # class A
15
+ # multimethod %q{
16
+ # def foo(x) # matches any argument type
17
+ # "#{x.inspect}"
18
+ # end
19
+ # }
20
+ #
21
+ # multimethod %q{
22
+ # def foo(Fixnum x) # matches any Fixnum
23
+ # "Fixnum #{x.inspect}"
24
+ # end
25
+ # }
26
+ #
27
+ # multimethod %q{
28
+ # def foo(Numeric x) # matches any Numeric
29
+ # "Numeric #{x.inspect}"
30
+ # end
31
+ # }
32
+ # end
33
+ #
34
+ # a = A.new
35
+ # puts a.foo(:symbol) # ==> ":symbol"
36
+ # puts a.foo(45) # ==> "Fixnum 45"
37
+ # puts a.foo(12.34) # ==> "Numeric 12.34"
38
+ #
39
+ # == Known Issues
40
+ #
41
+ # This library is not yet thread-safe, due to caching mechanisms
42
+ # used to increase performance. This will be fixed in a future release.
43
+ #
44
+ # == Home page
45
+ #
46
+ # * {Multimethod Home}[http://multimethod.rubyforge.org]
47
+ #
48
+ # == Credits
49
+ #
50
+ # Multimethod was developed by:
51
+ #
52
+ # * Kurt Stephens -- ruby-multimethod(at)umleta.com, sponsored by umleta.com
53
+ #
54
+ # == Contributors
55
+ #
56
+ # Maybe you?
57
+ #
58
+ # == See Also
59
+ #
60
+ # * http://en.wikipedia.org/wiki/Multimethod
61
+ # * http://rubyforge.org/projects/multi/
62
+ #
63
+ module Multimethod
64
+ end
65
+
1
66
  $:.unshift(File.dirname(__FILE__)) unless
2
67
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
68
 
@@ -7,3 +72,4 @@ require 'multimethod/method'
7
72
  require 'multimethod/signature'
8
73
  require 'multimethod/parameter'
9
74
  require 'multimethod/core_extensions'
75
+
@@ -1,5 +1,6 @@
1
1
  module Multimethod
2
2
 
3
+ # See Multimethod::ObjectExtension::ClassMethods
3
4
  module ObjectExtension
4
5
  def self.append_features(base) # :nodoc:
5
6
  # puts "append_features{#{base}}"
@@ -7,7 +8,25 @@ module Multimethod
7
8
  base.extend(ClassMethods)
8
9
  end
9
10
 
11
+
12
+ # This module is included into Object
13
+ # It is the "glue" for Multmethod.
10
14
  module ClassMethods
15
+ # Installs a new Multimethod Method using the multimethod syntax:
16
+ #
17
+ # class A
18
+ # multimethod q{
19
+ # def foo(x)
20
+ # ...
21
+ # end
22
+ # }
23
+ # multimethod q{
24
+ # def foo(A x)
25
+ # end
26
+ # }
27
+ # end
28
+ #
29
+ # Interfaces to Multimethod::Table.instance.
11
30
  def multimethod(body, file = nil, line = nil)
12
31
  unless file && line
13
32
  fileline = caller(1)[0]
@@ -24,12 +43,17 @@ module Multimethod
24
43
 
25
44
  ::Multimethod::Table.instance.install_method(self, body, file, line)
26
45
  end
27
- end
28
46
 
29
- def remove_multimethod(signature)
30
- ::Multimethod::Table.instance.remove_method(signature)
31
- end
47
+ # Removes a Multimethod using a signature:
48
+ #
49
+ # class A
50
+ # remove_multimethod "def foo(A x)"
51
+ # end
52
+ def remove_multimethod(signature)
53
+ ::Multimethod::Table.instance.remove_method(signature)
54
+ end
32
55
 
56
+ end # mixin
33
57
  end # class
34
58
  end # module
35
59
 
@@ -1,11 +1,29 @@
1
1
  module Multimethod
2
2
 
3
+ # Represents a Method implementation in a Multimethod.
4
+ #
5
+ # A Method is bound to a Module using a unique implementation name for the Multimethod.
6
+ #
7
+ # A Multimethod may have multiple implementation Methods.
8
+ #
9
+ # The Multimethod is responsible for determining the correct Method based on the
10
+ # relative scoring of the Method Signature.
11
+ #
3
12
  class Method
13
+ # The Method's Signature used for relative scoring of applicability to an argument list.
4
14
  attr_accessor :signature
15
+
16
+ # The Method's underlying method name.
17
+ # This method name is unique.
5
18
  attr_accessor :impl_name
6
19
 
20
+ # The Method's Multimethod.
7
21
  attr_accessor :multimethod
8
22
 
23
+ # Initialize a new Method.
24
+ #
25
+ # Method.new(impl_name, signature)
26
+ # Method.new(impl_name, mod, name, parameter_list)
9
27
  def initialize(impl_name, *args)
10
28
  if args.size == 1
11
29
  @signature = args[0]
@@ -23,19 +41,20 @@ module Multimethod
23
41
  end
24
42
 
25
43
 
44
+ # Returns true if this Method matches the Signature.
26
45
  def matches_signature(signature)
27
46
  @signature == signature
28
47
  end
29
48
 
30
49
 
50
+ # Remove the method implementation from the receiver Module.
31
51
  def remove_implementation
32
- # Remove the method implementation
33
52
  # $stderr.puts "Removing implementation for #{signature.to_s} => #{impl_name}"
34
53
  signature.mod.class_eval("remove_method #{impl_name.inspect}")
35
54
  end
36
55
 
37
56
 
38
- # For score sort
57
+ # Returns 0.
39
58
  def <=>(x)
40
59
  0
41
60
  end
@@ -47,40 +66,50 @@ module Multimethod
47
66
  end
48
67
 
49
68
 
50
- # Scoring this method.
69
+ # Score of this Method based on the argument types.
70
+ # The receiver type is the first element of args.
51
71
  def score(args)
52
72
  @signature.score(args)
53
73
  end
54
74
 
55
75
 
76
+ # Score this Method based on the argument types
77
+ # using a cache.
56
78
  def score_cached(args)
57
79
  @signature.score_cached(args)
58
80
  end
59
81
 
60
82
 
83
+ # Returns a string representation using the
84
+ # implementation name.
61
85
  def to_s(name = nil)
62
86
  name ||= @impl_name
63
87
  @signature.to_s(name)
64
88
  end
65
89
 
66
90
 
91
+ # Returns the "def foo(...)" string
92
+ # using the implementation name by default.
67
93
  def to_ruby_def(name = nil)
68
94
  name ||= @impl_name
69
95
  @signature.to_ruby_def(name)
70
96
  end
71
97
 
72
98
 
99
+ # Returns a ruby signature
100
+ # using the implementation name by default.
73
101
  def to_ruby_signature(name = nil)
74
102
  name ||= @impl_name
75
103
  @signature.to_ruby_signature(name)
76
104
  end
77
105
 
78
106
 
107
+ # Returns a string representing the Ruby parameters.
79
108
  def to_ruby_arg
80
109
  @signature.to_ruby_arg
81
110
  end
82
111
 
83
-
112
+ # Same as #to_s.
84
113
  def inspect
85
114
  to_s
86
115
  end
@@ -1,10 +1,23 @@
1
1
  module Multimethod
2
+ # Represents a Multimethod.
3
+ #
4
+ # A Multimethod has multiple implementations of a method based on the relative scoring
5
+ # of the Methods based on the argument types of the message.
6
+ #
7
+ # A Multimethod has a name.
8
+ #
2
9
  class Multimethod
3
10
 
11
+ # The Multimethod's name.
4
12
  attr_accessor :name
13
+
14
+ # A list of Method's that implement this Multimethod.
5
15
  attr_accessor :method
16
+
17
+ # The Multimethod::Table that owns this Multimethod.
6
18
  attr_accessor :table
7
19
 
20
+ # Initialize a new Multimethod.
8
21
  def initialize(name, *opts)
9
22
  raise NameError, "multimethod name not specified" unless name && name.to_s.size > 0
10
23
 
@@ -17,19 +30,22 @@ module Multimethod
17
30
  end
18
31
 
19
32
 
33
+ # Generates a unique symbol for a method name.
34
+ # Method implementations will use a unique name for the implementation method.
35
+ # For example, for a Multimethod named "foo", the Method name might be "_multimethod_12_foo".
20
36
  def gensym(name = nil)
21
37
  name ||= @name
22
38
  "_multimethod_#{@name_i = @name_i + 1}_#{name}"
23
39
  end
24
40
 
25
-
41
+ # Creates a new Method object bound to mod by name.
26
42
  def new_method(mod, name, *args)
27
43
  m = Method.new(gensym(name), mod, name, *args)
28
44
  add_method(m)
29
45
  m
30
46
  end
31
47
 
32
-
48
+ # Create a new Method object using the Signature.
33
49
  def new_method_from_signature(signature)
34
50
  m = Method.new(gensym(name), signature)
35
51
  add_method(m)
@@ -37,6 +53,7 @@ module Multimethod
37
53
  end
38
54
 
39
55
 
56
+ # Adds the new Method object to this Multimethod.
40
57
  def add_method(method)
41
58
  # THREAD CRITICAL BEGIN
42
59
  remove_method(method.signature)
@@ -47,18 +64,20 @@ module Multimethod
47
64
  end
48
65
 
49
66
 
67
+ # Returns true if this Multimethod matches the Signature.
50
68
  def matches_signature(signature)
51
69
  @name == signature.name
52
70
  end
53
71
 
54
72
 
73
+ # Returns a list of all Methods that match the Signature.
55
74
  def find_method(signature)
56
75
  m = @method.select{|x| x.matches_signature(signature)}
57
76
 
58
77
  m
59
78
  end
60
79
 
61
-
80
+ # Removes the method.
62
81
  def remove_method(x)
63
82
  case x
64
83
  when Signature
@@ -139,6 +158,8 @@ module Multimethod
139
158
  end
140
159
 
141
160
 
161
+ # Returns a sorted list of scores and Methods that
162
+ # match the argument types.
142
163
  def score_methods(meths, args)
143
164
  scores = meths.collect do |meth|
144
165
  score = meth.score_cached(args)
@@ -154,23 +175,14 @@ module Multimethod
154
175
  scores.compact!
155
176
  scores.sort!
156
177
 
157
- # $stderr.puts "score_methods(#{args.inspect}) => \n#{scores.inspect}"
178
+ # $stderr.puts %{ score_methods(#{args.inspect}) => \n#{scores.collect{|x| x.inspect}.join("\n")}}
158
179
 
159
180
  scores
160
181
  end
161
182
 
162
183
 
163
- def remove_dispatch(mod)
164
- # THREAD CRITICAL BEGIN
165
- if @dispatch[mod]
166
- @dispatch[mod] = false
167
- # $stderr.puts "Removing dispatch for #{mod.name}##{name}"
168
- mod.class_eval("remove_method #{name.inspect}")
169
- end
170
- # THREAD CRITICAL END
171
- end
172
-
173
-
184
+ # Installs a dispatching method in the Module.
185
+ # This method will dispatch to the Multimethod for Method lookup and application.
174
186
  def install_dispatch(mod)
175
187
  # THREAD CRITICAL BEGIN
176
188
  unless @dispatch[mod]
@@ -187,6 +199,18 @@ end_eval
187
199
  end
188
200
 
189
201
 
202
+ # Removes the dispatching method in the Module.
203
+ def remove_dispatch(mod)
204
+ # THREAD CRITICAL BEGIN
205
+ if @dispatch[mod]
206
+ @dispatch[mod] = false
207
+ # $stderr.puts "Removing dispatch for #{mod.name}##{name}"
208
+ mod.class_eval("remove_method #{name.inspect}")
209
+ end
210
+ # THREAD CRITICAL END
211
+ end
212
+
213
+
190
214
  ##################################################
191
215
  # Support
192
216
  #
@@ -1,6 +1,6 @@
1
+ module Multimethod
1
2
  # DO NOT EDIT
2
3
  # This file is auto-generated by build scripts.
3
4
  # See: rake update_version
4
- module Multimethod
5
- MultimethodVersion = '0.1.0'
5
+ MultimethodVersion = '0.2.0'
6
6
  end
@@ -1,18 +1,51 @@
1
1
  module Multimethod
2
+
3
+ # Represents a Parameter in a Signature.
4
+ #
5
+ # A Parameter has a name, type and position.
6
+ #
7
+ # Parameters may also have a default value or may be a restarg, a parameter that
8
+ # collects all remaining arguments.
9
+ #
10
+ # Restarg parameters have a lower score than other arguments.
11
+ #
12
+ # Unlike Ruby parameters, Parameters are typed. Unspecified Parameter types default to Kernel.
13
+ #
2
14
  class Parameter
3
15
  include Comparable
4
16
 
5
- RESTARG_SCORE = 9999
17
+ # The score base used for all Parameters with defaults.
18
+ DEFAULT_SCORE_BASE = 200
19
+
20
+ # The score used for all Parameters with defaults and no argument.
21
+ DEFAULT_SCORE = DEFAULT_SCORE_BASE + 100
6
22
 
23
+ # The score used for all restarg Parameters.
24
+ RESTARG_SCORE = DEFAULT_SCORE + 100
25
+
26
+ # The Parameter name.
7
27
  attr_accessor :name
28
+
29
+ # The Parameter's offset in the Signature's parameter list.
30
+ # Parameter 0 is the implied "self" Parameter.
8
31
  attr_accessor :i
32
+
33
+ # The Paremeter's type, defaults to Kernel.
9
34
  attr_accessor :type
35
+
36
+ # The Parameter's default value expression.
10
37
  attr_accessor :default
38
+
39
+ # True if the Parameter is a restarg: e.g.: "*args"
11
40
  attr_accessor :restarg
12
41
 
42
+ # The Parameter's owning Signature.
13
43
  attr_accessor :signature
44
+
45
+ # Defines level of verbosity during processing.
14
46
  attr_accessor :verbose
15
47
 
48
+ # Initialize a new Parameter.
16
49
  def initialize(name = nil, type = nil, default = nil, restarg = false)
17
50
  # $stderr.puts "initialize(#{name.inspect}, #{type}, #{default.inspect}, #{restarg.inspect})"
18
51
  if name
@@ -44,7 +77,8 @@ module Multimethod
44
77
  @name = name
45
78
  end
46
79
 
47
-
80
+ # Compare two Parameters.
81
+ # Only type and restarg are significant.
48
82
  def <=>(p)
49
83
  x = @type <=> p.type
50
84
  x = ! @restarg == ! p.restarg ? 0 : 1 if x == 0
@@ -54,6 +88,7 @@ module Multimethod
54
88
  end
55
89
 
56
90
 
91
+ # Scan a string for a Parameter specification.
57
92
  def scan_string(str, need_names = true)
58
93
  str.sub!(/\A\s+/, '')
59
94
 
@@ -130,17 +165,37 @@ module Multimethod
130
165
  end
131
166
 
132
167
 
168
+ # Returns the score of this Parameter matching an argument type.
169
+ #
170
+ # The score is determined by the relative distance of the Parameter
171
+ # to the argument type. A lower distance means a tighter match
172
+ # of this Parameter.
173
+ #
174
+ # Parameters with restargs or unspecfied default arguments score lower, see RESTARG_SCORE, DEFAULT_SCORE.
133
175
  def score(arg)
134
- return RESTARG_SCORE if @restarg
135
- score = all_types(arg).index(type_object)
176
+ if @restarg
177
+ score = RESTARG_SCORE
178
+ elsif @default && ! arg
179
+ score = DEFAULT_SCORE
180
+ else
181
+ score = all_types(arg).index(type_object)
182
+ end
183
+
184
+ # $stderr.puts " score(#{signature.to_s}, #{to_s}, #{arg && arg.name}) => #{score}"
185
+
186
+ score
136
187
  end
137
188
 
138
189
 
139
- def all_types(arg)
140
- arg.ancestors
190
+ # Returns a list of all parent Modules of an argument type,
191
+ # including itself, in most-specialized
192
+ # to least-specialized order.
193
+ def all_types(arg_type)
194
+ arg_type.ancestors
141
195
  end
142
196
 
143
197
 
198
+ # Resolves type by name
144
199
  def type_object
145
200
  if @type.kind_of?(String)
146
201
  @type = Table.instance.name_to_object(@type,
@@ -152,16 +207,20 @@ module Multimethod
152
207
  end
153
208
 
154
209
 
210
+ # Returns a String representing this Parameter in a Signature string.
155
211
  def to_s
156
212
  "#{@type} #{to_ruby_arg}"
157
213
  end
158
214
 
159
215
 
216
+ # Return a String representing this Parameter as a Ruby method parameter.
160
217
  def to_ruby_arg
161
218
  "#{to_s_name}#{@default ? ' = ' + @default : ''}"
162
219
  end
163
220
 
164
221
 
222
+ # Return a String representing this Parameter's name.
223
+ # Restargs will be prefixed with '*'.
165
224
  def to_s_name
166
225
  (@restarg ? "*" : '') + (@name.to_s || "_arg_#{@i}")
167
226
  end
@@ -1,24 +1,51 @@
1
1
  module Multimethod
2
2
 
3
+ # Represents a method signature.
4
+ #
5
+ # A Signature has a bound Module, a name and a Parameter list.
6
+ #
7
+ # Each Parameter contributes to the scoring of the Method based
8
+ # on the message argument types, including the message receiver.
9
+ #
3
10
  class Signature
4
11
  include Comparable
5
12
 
6
- attr_accessor :mod # The Module the signature is bound to.
7
- attr_accessor :class_method # True if the signature is bound to the class.
8
- attr_accessor :name # The name of the method signature.
9
- attr_accessor :parameter # The parameters of the method, self included.
13
+ # The Module that the Signature is bound to.
14
+ attr_accessor :mod
10
15
 
11
- attr_accessor :min_args # The minimum # of arguments for this signature
12
- attr_accessor :max_args # The maximum # of arguments for this signature;
13
- # May be nil, if restargs
14
- attr_accessor :restarg # The "*args" parameter or nil
15
- attr_accessor :default # The first parameter with a default value.
16
+ # True if the signature is bound to the class.
17
+ attr_accessor :class_method
16
18
 
19
+ # The name of the method.
20
+ attr_accessor :name
21
+
22
+ # The list of Parameters, self is included at position 0.
23
+ attr_accessor :parameter
24
+
25
+ # The minimum # of arguments for this signature.
26
+ attr_accessor :min_args
27
+
28
+ # The maximum # of arguments for this signature.
29
+ # May be nil, if this Signature accepts restargs.
30
+ attr_accessor :max_args
31
+
32
+ # The "*args" parameter or nil.
33
+ attr_accessor :restarg
34
+
35
+ # An Array of all Parameters with a default value.
36
+ # Will be nil if there is not a Parameter with a default values.
37
+ attr_accessor :default
38
+
39
+ # The file where this Signature is specified.
17
40
  attr_accessor :file
41
+
42
+ # The line in the file where this Signature is specified.
18
43
  attr_accessor :line
19
44
 
45
+ # Defines level of verbosity during processing.
20
46
  attr_accessor :verbose
21
47
 
48
+ # Initialize a new Signature.
22
49
  def initialize(*opts)
23
50
  opts = Hash[*opts]
24
51
 
@@ -53,7 +80,7 @@ module Multimethod
53
80
  end
54
81
 
55
82
 
56
- # For sort
83
+ # Compares two Signature objects.
57
84
  def <=>(s)
58
85
  x = @name.to_s <=> s.name.to_s
59
86
  x = (! @class_method == ! s.class_method ? 0 : 1) if x == 0
@@ -63,6 +90,7 @@ module Multimethod
63
90
  end
64
91
 
65
92
 
93
+ # Returns the bound Module.
66
94
  def mod
67
95
  # THREAD CRITICAL BEGIN
68
96
  if @mod && @mod.kind_of?(String)
@@ -76,7 +104,7 @@ module Multimethod
76
104
  end
77
105
 
78
106
 
79
- # Scan
107
+ # Scan a string as a Signature, e.g.: "def foo(A a, x = true, *restargs)"
80
108
  def scan_string(str, need_names = true)
81
109
 
82
110
  str.sub!(/\A\s+/, '')
@@ -124,6 +152,9 @@ module Multimethod
124
152
  end
125
153
 
126
154
 
155
+ # Scan the parameter string of a Signature:
156
+ #
157
+ # "A a, x = true, *restargs"
127
158
  def scan_parameters_string(str, need_names = true)
128
159
  # @verbose = true
129
160
 
@@ -161,6 +192,10 @@ module Multimethod
161
192
  end
162
193
 
163
194
 
195
+ # Scan a programmatic Parameter list:
196
+ #
197
+ # [ A, :a, B, :b, :c, '*d' ]
198
+ #
164
199
  def scan_parameters(params)
165
200
  # Add self parameter at front.
166
201
  add_self
@@ -194,12 +229,13 @@ module Multimethod
194
229
  end
195
230
 
196
231
 
197
- # Add self parameter at front.
232
+ # Add the implicit "self" parameter at the front of the Parameter list.
198
233
  def add_self
199
234
  add_parameter(Parameter.new('self', mod)) if @parameter.empty?
200
235
  end
201
236
 
202
-
237
+
238
+ # Adds a new Parameter.
203
239
  def add_parameter(p)
204
240
  if p.restarg
205
241
  raise("Too many restargs") if @restarg
@@ -224,17 +260,12 @@ module Multimethod
224
260
  end
225
261
 
226
262
 
227
- def score_cached(args)
228
- unless x = @score[args]
229
- x = @score[args] =
230
- score(args)
231
- end
232
- x
233
- end
234
-
235
-
263
+ # Score of this Signature based on the argument types.
264
+ #
265
+ # The score is an Array of values that when sorted against
266
+ # other Signature scores will
267
+ # place the best matching Signature at the top of the list.
236
268
  def score(args)
237
-
238
269
  if @min_args > args.size
239
270
  # Not enough args
240
271
  score = nil
@@ -251,22 +282,35 @@ module Multimethod
251
282
  if @restarg || @default
252
283
  while (i = i + 1) < @parameter.size
253
284
  # $stderr.puts " Adding score i=#{i}"
254
- score << parameter_at(i).score(NilClass)
285
+ # nil means there is no argument for this parameter.
286
+ score << parameter_at(i).score(nil)
255
287
  end
256
288
  end
257
289
 
258
- # If any argument cannot match, avoid this method.
290
+ # If any argument cannot match, avoid this method entirely.
291
+ score.flatten!
259
292
  score = nil if score.index(nil)
260
293
  end
261
294
 
262
- # if true || @name =~ /_bar$/
263
- # $stderr.puts " Method: score #{self.to_s} #{args.inspect} => #{score.inspect}"
264
- # end
295
+ # $stderr.puts " score(#{to_s}, #{args.inspect} => #{score.inspect})"
265
296
 
266
297
  score
267
298
  end
268
299
 
269
300
 
301
+ # Score of this Signature using a cache.
302
+ def score_cached(args)
303
+ unless x = @score[args]
304
+ x = @score[args] =
305
+ score(args)
306
+ end
307
+ x
308
+ end
309
+
310
+
311
+ # Returns the Parameter at argument position i.
312
+ # If the Signature has a restarg, it will be used for
313
+ # argument postitions past the end of the Parameter list.
270
314
  def parameter_at(i)
271
315
  if i >= @parameter.size && @restarg
272
316
  @restarg
@@ -276,6 +320,7 @@ module Multimethod
276
320
  end
277
321
 
278
322
 
323
+ # Returns a String representing this Signature.
279
324
  def to_s(name = nil)
280
325
  name ||= @name || '_'
281
326
  p = @parameter.clone
@@ -284,18 +329,21 @@ module Multimethod
284
329
  end
285
330
 
286
331
 
332
+ # Returns a String representing this Signature's Parameters.
287
333
  def parameter_to_s(p = nil)
288
334
  p ||= @parameter
289
335
  p.collect{|x| x.to_s}.join(', ')
290
336
  end
291
337
 
292
338
 
339
+ # Returns a String representing this Signature's definition in Ruby syntax.
293
340
  def to_ruby_def(name = nil)
294
341
  name ||= @name || '_'
295
342
  "def #{name}(#{to_ruby_arg})"
296
343
  end
297
344
 
298
345
 
346
+ # Returns a String representing this Signature's definition in Ruby Doc syntax.
299
347
  def to_ruby_signature(name = nil)
300
348
  name ||= @name || '_'
301
349
  p = @parameter.clone
@@ -305,13 +353,14 @@ module Multimethod
305
353
  end
306
354
 
307
355
 
356
+ # Returns a String representing this Signature's definition parameters in Ruby syntax.
308
357
  def to_ruby_arg
309
358
  x = @parameter.clone
310
359
  x.shift
311
360
  x.collect{|x| x.to_ruby_arg}.join(', ')
312
361
  end
313
362
 
314
-
363
+ # Calls #to_s.
315
364
  def inspect
316
365
  to_s
317
366
  end
@@ -1,16 +1,25 @@
1
1
  module Multimethod
2
+ # Represents a Multimethod repository.
3
+ #
4
+ # There is typically only one instance.
5
+ #
6
+ # It provides the interface to the core extensions.
7
+ #
2
8
  class Table
3
9
 
4
10
  @@instance = nil
11
+ # Returns the current instance or creates a new one.
5
12
  def self.instance
6
13
  # THREAD CRITICAL BEGIN
7
14
  @@instance ||= self.new
8
15
  # TRREAD CRITICAL END
9
16
  end
10
17
 
11
- @@hook_var = :@@__multimethods
12
-
13
- attr_accessor :method
18
+
19
+ # A list of all Multimethod objects.
20
+ attr_accessor :multimethod
21
+
22
+ # Creates a new Table object.
14
23
  def initialize(*opts)
15
24
  @multimethod_by_name = { }
16
25
  @multimethod = [ ]
@@ -20,7 +29,20 @@ module Multimethod
20
29
  end
21
30
 
22
31
 
23
- @@match_def = /^\s*def\s+(\w+)([(](.*)[)])?/
32
+ # Installs a new Multimethod Method using the multimethod syntax:
33
+ #
34
+ # class A
35
+ # multimethod q{
36
+ # def foo(x)
37
+ # ...
38
+ # end
39
+ # }
40
+ # multimethod q{
41
+ # def foo(A x)
42
+ # end
43
+ # }
44
+ # end
45
+ #
24
46
  # Interface to Multimethod::Module mixin multimethod
25
47
  def install_method(mod, body, file = nil, line = nil)
26
48
  file ||= __FILE__
@@ -55,12 +77,16 @@ module Multimethod
55
77
  end
56
78
 
57
79
 
80
+ # Returns the Multimethods that matches a signature.
81
+ # The signature can be a String, Method or Signature object.
58
82
  def find_multimethod(x)
59
83
  case x
60
84
  when String
61
85
  signature = Signature.new(:string => x)
62
- when Method
86
+ when Method
63
87
  signature = x.signature
88
+ when Signature
89
+ signature = x
64
90
  end
65
91
 
66
92
  x = @multimethod.select{|mm| mm.matches_signature(signature)}
@@ -69,12 +95,17 @@ module Multimethod
69
95
  end
70
96
 
71
97
 
98
+ # Returns a list of all the Methods that match a signature.
99
+ #
100
+ # The signature can be a String, Method or Signature object.
72
101
  def find_method(x)
73
102
  case x
74
103
  when String
75
104
  signature = Signature.new(:string => x)
76
105
  when Method
77
106
  signature = x.signature
107
+ when Signature
108
+ signature = x
78
109
  end
79
110
 
80
111
  x = @multimethod.select{|mm| mm.matches_signature(signature)}
@@ -86,6 +117,11 @@ module Multimethod
86
117
  end
87
118
 
88
119
 
120
+ # Removed the Method that match a signature.
121
+ #
122
+ # The signature can be a String, Method or Signature object.
123
+ #
124
+ # Raises an error if more than one Method is found.
89
125
  def remove_method(signature)
90
126
  x = find_method(signature)
91
127
  raise("Found #{x.size} multimethods: #{x.inspect}") if x.size > 1
@@ -94,6 +130,9 @@ module Multimethod
94
130
  end
95
131
 
96
132
 
133
+ # Returns a Multimethod object for a method name.
134
+ #
135
+ # Will create a new Multimethod if needed.
97
136
  def lookup_multimethod(name)
98
137
  name = name.to_s
99
138
 
@@ -110,7 +149,7 @@ module Multimethod
110
149
  end
111
150
 
112
151
 
113
- # Interface to code generated by #install_dispatch.
152
+ # Dispatches to the appropriate Method based on name, receiver and arguments.
114
153
  def dispatch(name, rcvr, args)
115
154
  unless mm = @multimethod_by_name[name]
116
155
  raise NameError, 'No method for multmethod #{name}' unless mm
@@ -123,6 +162,7 @@ module Multimethod
123
162
  # Support
124
163
  #
125
164
 
165
+ # Returns the object for name, using the appropriate evaluation scope.
126
166
  def name_to_object(name, scope = nil, file = nil, line = nil)
127
167
  scope ||= Kernel
128
168
  # THREAD CRITICAL BEGIN
@@ -47,39 +47,54 @@ class C < Object
47
47
  include Comparable
48
48
  end
49
49
 
50
+
50
51
  class D < B
51
52
  # Variadic
52
53
  multimethod %q{
53
- def bar(x)
54
- x = "D#bar(x) : (#{x.class.name})"
54
+ def bbb(x)
55
+ x = "D#bbb(x) : (#{x.class.name})"
56
+ x
57
+ end
58
+ }
59
+
60
+ multimethod %q{
61
+ def bbb(*rest)
62
+ x = "D#bbb(*rest) : (#{rest.collect{|x| x.class}.inspect})"
63
+ x
64
+ end
65
+ }
66
+
67
+ multimethod %q{
68
+ def bbb(x, y)
69
+ x = "D#bbb(x, y) : (#{x.class.name}, #{y.class.name})"
55
70
  x
56
71
  end
57
72
  }
58
73
 
59
74
  multimethod %q{
60
- def bar(*rest)
61
- x = "D#bar(*rest) : (#{rest.inspect})"
75
+ def bbb(Fixnum x, String y)
76
+ x = "D#bbb(Fixnum x, String y) : (#{x.class.name}, #{y.class.name})"
62
77
  x
63
78
  end
64
79
  }
65
80
 
66
81
  multimethod %q{
67
- def bar(x, y)
68
- x = "D#bar(x, y) : (#{x.class.name}, #{y.class.name})"
82
+ def bbb(Fixnum x, Fixnum y = 1)
83
+ x = "D#bbb(Fixnum x, Fixnum y = 1) : (#{x.class.name}, #{y.class.name})"
69
84
  x
70
85
  end
71
86
  }
72
87
 
73
88
  multimethod %q{
74
- def bar(x, y, A a)
75
- x = "D#bar(x, y, A a) : (#{x.class.name}, #{y.class.name}, #{a.class.name})"
89
+ def bbb(x, String y, A a)
90
+ x = "D#bbb(x, String y, A a) : (#{x.class.name}, #{y.class.name}, #{a.class.name})"
76
91
  x
77
92
  end
78
93
  }
79
94
 
80
95
  multimethod %q{
81
- def bar(x, y, *rest)
82
- x = "D#bar(x, y, *rest) : (#{x.class.name}, #{y.class.name}, #{rest.inspect})"
96
+ def bbb(Fixnum x, y, *rest)
97
+ x = "D#bbb(Fixnum x, y, *rest) : (#{x.class.name}, #{y.class.name}, #{rest.collect{|x| x.class}.inspect})"
83
98
  x
84
99
  end
85
100
  }
@@ -125,20 +140,38 @@ module Multimethod
125
140
  a = A.new
126
141
  d = D.new
127
142
 
128
- assert_equal 'D#bar(*rest) : ([])',
129
- d.bar()
143
+ assert_not_nil bbb_mm = ::Multimethod::Table.instance.multimethod.select{|mm| mm.name == 'bbb'}
144
+ assert_equal 1, bbb_mm.size
145
+ assert_kind_of ::Multimethod::Multimethod, bbb_mm = bbb_mm[0]
146
+
147
+ assert_equal 7, bbb_mm.method.size
148
+
149
+ assert_equal 'D#bbb(*rest) : ([])',
150
+ d.bbb()
151
+
152
+ assert_equal 'D#bbb(x) : (Symbol)',
153
+ d.bbb(:x)
154
+
155
+ assert_equal 'D#bbb(Fixnum x, String y) : (Fixnum, String)' ,
156
+ d.bbb(1, 'a')
157
+
158
+ assert_equal 'D#bbb(Fixnum x, Fixnum y = 1) : (Fixnum, Fixnum)' ,
159
+ d.bbb(1, 2)
160
+
161
+ assert_equal 'D#bbb(x, y) : (Symbol, Symbol)' ,
162
+ d.bbb(:x, :y)
130
163
 
131
- assert_equal 'D#bar(x) : (Fixnum)',
132
- d.bar(1)
164
+ assert_equal 'D#bbb(*rest) : ([Symbol, D, D])' ,
165
+ d.bbb(:x, d, d)
133
166
 
134
- assert_equal 'D#bar(x, y) : (Fixnum, String)' ,
135
- d.bar(1, 'a')
167
+ assert_equal 'D#bbb(Fixnum x, y, *rest) : (Fixnum, String, [A])' ,
168
+ d.bbb(1, 'a', a)
136
169
 
137
- assert_equal 'D#bar(x, y, A a) : (Fixnum, String, A)' ,
138
- d.bar(1, 'a', a)
170
+ assert_equal 'D#bbb(x, String y, A a) : (String, String, A)' ,
171
+ d.bbb('a', 'b', a)
139
172
 
140
- assert_equal 'D#bar(x, y, *rest) : (Fixnum, String, [3])' ,
141
- d.bar(1, 'a', 3)
173
+ assert_equal 'D#bbb(Fixnum x, y, *rest) : (Fixnum, String, [Fixnum])' ,
174
+ d.bbb(1, 'a', 3)
142
175
  end
143
176
 
144
177
  end # class
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: multimethod
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2006-11-24 00:00:00 -05:00
6
+ version: 0.2.0
7
+ date: 2006-11-29 00:00:00 -05:00
8
8
  summary: "Supports Multimethod dispatching. For more details, see: http://multimethod.rubyforge.org/files/lib/multimethod_rb.html http://multimethod.rubyforge.org/files/README.txt http://multimethod.rubyforge.org/"
9
9
  require_paths:
10
10
  - lib