multimethod 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|