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 +5 -0
- data/README.txt +1 -1
- data/Rakefile +4 -4
- data/Releases.txt +9 -2
- data/lib/multimethod.rb +66 -0
- data/lib/multimethod/core_extensions.rb +28 -4
- data/lib/multimethod/method.rb +33 -4
- data/lib/multimethod/multimethod.rb +39 -15
- data/lib/multimethod/multimethod_version.rb +2 -2
- data/lib/multimethod/parameter.rb +65 -6
- data/lib/multimethod/signature.rb +78 -29
- data/lib/multimethod/table.rb +46 -6
- data/test/usage_test.rb +53 -20
- metadata +2 -2
data/ChangeLog
CHANGED
data/README.txt
CHANGED
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 =
|
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 =
|
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
|
data/Releases.txt
CHANGED
@@ -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
|
-
*
|
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
|
-
*
|
21
|
+
* Fix how default parameters are scored.
|
data/lib/multimethod.rb
CHANGED
@@ -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
|
-
|
30
|
-
|
31
|
-
|
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
|
|
data/lib/multimethod/method.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
|
164
|
-
|
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,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
|
-
|
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
|
-
|
135
|
-
|
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
|
-
|
140
|
-
|
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
|
-
|
7
|
-
attr_accessor :
|
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
|
-
|
12
|
-
attr_accessor :
|
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
|
-
#
|
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
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
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
|
-
#
|
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
|
data/lib/multimethod/table.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
attr_accessor :
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
data/test/usage_test.rb
CHANGED
@@ -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
|
54
|
-
x = "D#
|
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
|
61
|
-
x = "D#
|
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
|
68
|
-
x = "D#
|
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
|
75
|
-
x = "D#
|
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
|
82
|
-
x = "D#
|
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
|
-
|
129
|
-
|
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#
|
132
|
-
d.
|
164
|
+
assert_equal 'D#bbb(*rest) : ([Symbol, D, D])' ,
|
165
|
+
d.bbb(:x, d, d)
|
133
166
|
|
134
|
-
assert_equal 'D#
|
135
|
-
d.
|
167
|
+
assert_equal 'D#bbb(Fixnum x, y, *rest) : (Fixnum, String, [A])' ,
|
168
|
+
d.bbb(1, 'a', a)
|
136
169
|
|
137
|
-
assert_equal 'D#
|
138
|
-
d.
|
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#
|
141
|
-
d.
|
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.
|
7
|
-
date: 2006-11-
|
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
|