multimethod 0.0.1 → 0.1.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/Manifest.txt +21 -54
- data/Rakefile +19 -2
- data/Releases.txt +7 -0
- data/lib/multimethod.rb +1 -0
- data/lib/multimethod/core_extensions.rb +6 -1
- data/lib/multimethod/method.rb +43 -185
- data/lib/multimethod/multimethod.rb +58 -3
- data/lib/multimethod/multimethod_version.rb +6 -0
- data/lib/multimethod/parameter.rb +124 -17
- data/lib/multimethod/signature.rb +322 -0
- data/lib/multimethod/table.rb +75 -30
- data/test/add_remove_test.rb +99 -0
- data/test/method_test.rb +2 -67
- data/test/multimethod_test.rb +18 -16
- data/test/signature_test.rb +190 -0
- metadata +23 -56
- data/.svn/README.txt +0 -2
- data/.svn/empty-file +0 -0
- data/.svn/entries +0 -68
- data/.svn/format +0 -1
- data/.svn/text-base/ChangeLog.svn-base +0 -3
- data/.svn/text-base/Manifest.txt.svn-base +0 -54
- data/.svn/text-base/README.txt.svn-base +0 -40
- data/.svn/text-base/Rakefile.svn-base +0 -132
- data/.svn/text-base/Releases.txt.svn-base +0 -7
- data/examples/.svn/README.txt +0 -2
- data/examples/.svn/empty-file +0 -0
- data/examples/.svn/entries +0 -22
- data/examples/.svn/format +0 -1
- data/examples/.svn/props/ex1.rb.svn-work +0 -5
- data/lib/.svn/README.txt +0 -2
- data/lib/.svn/empty-file +0 -0
- data/lib/.svn/entries +0 -24
- data/lib/.svn/format +0 -1
- data/lib/.svn/text-base/multimethod.rb.svn-base +0 -8
- data/lib/multimethod/.svn/README.txt +0 -2
- data/lib/multimethod/.svn/empty-file +0 -0
- data/lib/multimethod/.svn/entries +0 -53
- data/lib/multimethod/.svn/format +0 -1
- data/lib/multimethod/.svn/text-base/core_extensions.rb.svn-base +0 -38
- data/lib/multimethod/.svn/text-base/method.rb.svn-base +0 -232
- data/lib/multimethod/.svn/text-base/multimethod.rb.svn-base +0 -171
- data/lib/multimethod/.svn/text-base/parameter.rb.svn-base +0 -64
- data/lib/multimethod/.svn/text-base/table.rb.svn-base +0 -99
- data/test/.svn/README.txt +0 -2
- data/test/.svn/empty-file +0 -0
- data/test/.svn/entries +0 -53
- data/test/.svn/format +0 -1
- data/test/.svn/text-base/method_test.rb.svn-base +0 -89
- data/test/.svn/text-base/multimethod_test.rb.svn-base +0 -92
- data/test/.svn/text-base/parameter_test.rb.svn-base +0 -31
- data/test/.svn/text-base/test_base.rb.svn-base +0 -25
- data/test/.svn/text-base/usage_test.rb.svn-base +0 -146
@@ -1,5 +1,7 @@
|
|
1
1
|
module Multimethod
|
2
2
|
class Parameter
|
3
|
+
include Comparable
|
4
|
+
|
3
5
|
RESTARG_SCORE = 9999
|
4
6
|
|
5
7
|
attr_accessor :name
|
@@ -8,21 +10,123 @@ module Multimethod
|
|
8
10
|
attr_accessor :default
|
9
11
|
attr_accessor :restarg
|
10
12
|
|
11
|
-
attr_accessor :
|
13
|
+
attr_accessor :signature
|
14
|
+
attr_accessor :verbose
|
12
15
|
|
13
|
-
def initialize(name, type = nil, default = nil, restarg = false)
|
14
|
-
# $stderr.puts "initialize(#{name.inspect}, #{type}, #{restarg.inspect})"
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
16
|
+
def initialize(name = nil, type = nil, default = nil, restarg = false)
|
17
|
+
# $stderr.puts "initialize(#{name.inspect}, #{type}, #{default.inspect}, #{restarg.inspect})"
|
18
|
+
if name
|
19
|
+
# Default type if name is specified
|
20
|
+
type ||= Kernel
|
21
|
+
end
|
19
22
|
|
20
|
-
name = name.intern unless name.kind_of?(Symbol)
|
21
|
-
@name = name
|
22
23
|
@i = nil
|
23
|
-
@type = type
|
24
|
+
@type = type
|
24
25
|
@default = default
|
25
26
|
@restarg = restarg
|
27
|
+
@verbose = false
|
28
|
+
|
29
|
+
self.name = name # may affect @restarg
|
30
|
+
|
31
|
+
@signature = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def name=(name)
|
36
|
+
if name
|
37
|
+
name = name.to_s
|
38
|
+
if name.sub!(/\A\*/, '')
|
39
|
+
@restarg = true
|
40
|
+
end
|
41
|
+
|
42
|
+
name = name.intern
|
43
|
+
end
|
44
|
+
@name = name
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def <=>(p)
|
49
|
+
x = @type <=> p.type
|
50
|
+
x = ! @restarg == ! p.restarg ? 0 : 1 if x == 0
|
51
|
+
x = ! @default == ! p.default ? 0 : 1 if x == 0
|
52
|
+
# $stderr.puts "#{to_s} <=> #{p.to_s} => #{x.inspect}"
|
53
|
+
x
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def scan_string(str, need_names = true)
|
58
|
+
str.sub!(/\A\s+/, '')
|
59
|
+
|
60
|
+
$stderr.puts " str=#{str.inspect}" if @verbose
|
61
|
+
|
62
|
+
if md = /\A(\w+(::\w+)*)\s+(\w+)/s.match(str)
|
63
|
+
# $stderr.puts " pre_match=#{md.pre_match.inspect}"
|
64
|
+
# $stderr.puts " md[0]=#{md[0].inspect}"
|
65
|
+
str = md.post_match
|
66
|
+
type = md[1]
|
67
|
+
name = md[3]
|
68
|
+
elsif md = /\A(\*?\w+)/s.match(str)
|
69
|
+
# $stderr.puts " pre_match=#{md.pre_match.inspect}"
|
70
|
+
# $stderr.puts " md[0]=#{md[0].inspect}"
|
71
|
+
str = md.post_match
|
72
|
+
type = nil
|
73
|
+
name = md[1]
|
74
|
+
else
|
75
|
+
raise NameError, "Syntax error in multimethod parameter: expected type and/or name at #{str.inspect}"
|
76
|
+
end
|
77
|
+
|
78
|
+
$stderr.puts " type=#{type.inspect}" if @verbose
|
79
|
+
$stderr.puts " name=#{name.inspect}" if @verbose
|
80
|
+
|
81
|
+
# Parse parameter default.
|
82
|
+
if md = /\A\s*=\s*/.match(str)
|
83
|
+
str = md.post_match
|
84
|
+
|
85
|
+
in_paren = 0
|
86
|
+
default = ''
|
87
|
+
until str.empty?
|
88
|
+
# $stderr.puts " default: str=#{str.inspect}"
|
89
|
+
# $stderr.puts " default: params=#{parameter_to_s}"
|
90
|
+
|
91
|
+
if md = /\A(\s+)/s.match(str)
|
92
|
+
str = md.post_match
|
93
|
+
default = default + md[1]
|
94
|
+
end
|
95
|
+
|
96
|
+
if md = /\A("([^"\\]|\\.)*")/s.match(str)
|
97
|
+
str = md.post_match
|
98
|
+
default = default + md[1]
|
99
|
+
elsif md = /\A('([^'\\]|\\.)*')/s.match(str)
|
100
|
+
str = md.post_match
|
101
|
+
default = default + md[1]
|
102
|
+
elsif md = /\A(\()/.match(str)
|
103
|
+
str = md.post_match
|
104
|
+
in_paren = in_paren + 1
|
105
|
+
default = default + md[1]
|
106
|
+
elsif in_paren > 0 && md = /\A(\))/s.match(str)
|
107
|
+
str = md.post_match
|
108
|
+
in_paren = in_paren - 1
|
109
|
+
default = default + md[1]
|
110
|
+
elsif md = /\A(\))/s.match(str)
|
111
|
+
break
|
112
|
+
elsif in_paren == 0 && md = /\A,/s.match(str)
|
113
|
+
break
|
114
|
+
elsif md = /\A(\w+)/s.match(str)
|
115
|
+
str = md.post_match
|
116
|
+
default = default + md[1]
|
117
|
+
elsif md = /\A(.)/s.match(str)
|
118
|
+
str = md.post_match
|
119
|
+
default = default + md[1]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
self.name = name unless @name
|
125
|
+
type ||= Kernel
|
126
|
+
self.type = type unless @type
|
127
|
+
self.default = default unless @default
|
128
|
+
|
129
|
+
str
|
26
130
|
end
|
27
131
|
|
28
132
|
|
@@ -39,24 +143,27 @@ module Multimethod
|
|
39
143
|
|
40
144
|
def type_object
|
41
145
|
if @type.kind_of?(String)
|
42
|
-
@type =
|
146
|
+
@type = Table.instance.name_to_object(@type,
|
147
|
+
@signature.mod,
|
148
|
+
@signature.file,
|
149
|
+
@signature.line)
|
43
150
|
end
|
44
151
|
@type
|
45
152
|
end
|
46
153
|
|
47
154
|
|
48
155
|
def to_s
|
49
|
-
|
156
|
+
"#{@type} #{to_ruby_arg}"
|
50
157
|
end
|
158
|
+
|
51
159
|
|
52
|
-
|
53
|
-
|
54
|
-
"#{@type} #{to_ruby}"
|
160
|
+
def to_ruby_arg
|
161
|
+
"#{to_s_name}#{@default ? ' = ' + @default : ''}"
|
55
162
|
end
|
56
163
|
|
57
164
|
|
58
|
-
def
|
59
|
-
|
165
|
+
def to_s_name
|
166
|
+
(@restarg ? "*" : '') + (@name.to_s || "_arg_#{@i}")
|
60
167
|
end
|
61
168
|
|
62
169
|
end # class
|
@@ -0,0 +1,322 @@
|
|
1
|
+
module Multimethod
|
2
|
+
|
3
|
+
class Signature
|
4
|
+
include Comparable
|
5
|
+
|
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.
|
10
|
+
|
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
|
+
|
17
|
+
attr_accessor :file
|
18
|
+
attr_accessor :line
|
19
|
+
|
20
|
+
attr_accessor :verbose
|
21
|
+
|
22
|
+
def initialize(*opts)
|
23
|
+
opts = Hash[*opts]
|
24
|
+
|
25
|
+
@mod = opts[:mod]
|
26
|
+
@name = opts[:name]
|
27
|
+
@class_method = false
|
28
|
+
@parameter = [ ]
|
29
|
+
@min_args = 0
|
30
|
+
@max_args = 0
|
31
|
+
@restarg = nil
|
32
|
+
@default = nil
|
33
|
+
|
34
|
+
@multimethod = nil
|
35
|
+
|
36
|
+
@verbose = nil
|
37
|
+
|
38
|
+
@score = { }
|
39
|
+
|
40
|
+
# Handle a string representation of a signature.
|
41
|
+
case params = opts[:string]
|
42
|
+
when String
|
43
|
+
scan_string(params)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Handle other parameters.
|
47
|
+
case params = opts[:parameter]
|
48
|
+
when Array
|
49
|
+
scan_parameters(params)
|
50
|
+
when String
|
51
|
+
scan_parameters_string(params)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# For sort
|
57
|
+
def <=>(s)
|
58
|
+
x = @name.to_s <=> s.name.to_s
|
59
|
+
x = (! @class_method == ! s.class_method ? 0 : 1) if x == 0
|
60
|
+
x = @parameter <=> s.parameter if x == 0
|
61
|
+
# $stderr.puts "#{to_s} <=> #{s.to_s} => #{x.inspect}"
|
62
|
+
x
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def mod
|
67
|
+
# THREAD CRITICAL BEGIN
|
68
|
+
if @mod && @mod.kind_of?(String)
|
69
|
+
@mod = Table.instance.name_to_object(@mod,
|
70
|
+
nil,
|
71
|
+
file, line)
|
72
|
+
end
|
73
|
+
# THREAD CRITICAL END
|
74
|
+
|
75
|
+
@mod
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Scan
|
80
|
+
def scan_string(str, need_names = true)
|
81
|
+
|
82
|
+
str.sub!(/\A\s+/, '')
|
83
|
+
|
84
|
+
if md = /\A(\w+(::\w+)*)#(\w+)/.match(str)
|
85
|
+
str = md.post_match
|
86
|
+
@mod = md[1] unless @mod
|
87
|
+
@name = md[3]
|
88
|
+
elsif md = /\A(\w+(::\w+)*)\.(\w+)/.match(str)
|
89
|
+
str = md.post_match
|
90
|
+
@mod = md[1] unless @mod
|
91
|
+
@class_method = true
|
92
|
+
@name = md[3]
|
93
|
+
elsif md = /\A((\w+(::\w+)*)\s+)?def\s+(self\.)?(\w+)/.match(str)
|
94
|
+
str = md.post_match
|
95
|
+
@mod = md[2] unless @mod
|
96
|
+
@class_method = ! ! md[4]
|
97
|
+
@name = md[5]
|
98
|
+
else
|
99
|
+
raise NameError, "Syntax error in multimethod signature at #{str.inspect}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Resolve mod name.
|
103
|
+
# FIXME!
|
104
|
+
|
105
|
+
# Add self parameter.
|
106
|
+
add_self
|
107
|
+
|
108
|
+
# Parse parameter list.
|
109
|
+
if md = /\A\(/.match(str)
|
110
|
+
str = md.post_match
|
111
|
+
|
112
|
+
str = scan_parameters_string(str, need_names)
|
113
|
+
|
114
|
+
$stderr.puts " str=#{str.inspect}" if @verbose
|
115
|
+
|
116
|
+
if md = /\A\)/.match(str)
|
117
|
+
str = md.post_match
|
118
|
+
else
|
119
|
+
raise NameError, "Syntax error in multimethod parameters expected ')' at #{str.inspect}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
str
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def scan_parameters_string(str, need_names = true)
|
128
|
+
# @verbose = true
|
129
|
+
|
130
|
+
# Add self parameter at front.
|
131
|
+
add_self
|
132
|
+
|
133
|
+
$stderr.puts "scan_parameters_string(#{str.inspect})" if @verbose
|
134
|
+
|
135
|
+
until str.empty?
|
136
|
+
# Scan parameter
|
137
|
+
p = Parameter.new
|
138
|
+
p.verbose = @verbose
|
139
|
+
str = p.scan_string(str)
|
140
|
+
add_parameter(p)
|
141
|
+
$stderr.puts " params=#{parameter_to_s}" if @verbose
|
142
|
+
|
143
|
+
# Parse , or )
|
144
|
+
str.sub!(/\A\s+/, '')
|
145
|
+
if ! str.empty?
|
146
|
+
if md = /\A,/s.match(str)
|
147
|
+
str = md.post_match
|
148
|
+
elsif md = /\A\)/s.match(str)
|
149
|
+
$stderr.puts " DONE: #{to_s}\n Remaining: #{str.inspect}" if @verbose
|
150
|
+
break
|
151
|
+
else
|
152
|
+
raise NameError, "Syntax error in multimethod parameters: expected ',' or ')' at #{str.inspect}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
$stderr.puts "scan_parameters_string(...): DONE: #{to_s}\n Remaining: #{str}" if @verbose
|
159
|
+
|
160
|
+
str
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def scan_parameters(params)
|
165
|
+
# Add self parameter at front.
|
166
|
+
add_self
|
167
|
+
|
168
|
+
until params.empty?
|
169
|
+
name = nil
|
170
|
+
type = nil
|
171
|
+
restarg = false
|
172
|
+
default = nil
|
173
|
+
|
174
|
+
if x = params.shift
|
175
|
+
case x
|
176
|
+
when Class
|
177
|
+
type = x
|
178
|
+
else
|
179
|
+
name = x
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if ! name && (x = params.shift)
|
184
|
+
name = x
|
185
|
+
end
|
186
|
+
|
187
|
+
raise("Parameter name expected, found #{name.inspect}") unless name.kind_of?(String) || name.kind_of?(Symbol)
|
188
|
+
raise("Parameter type expected, found #{type.inspect}") unless type.kind_of?(Module) || type.nil?
|
189
|
+
|
190
|
+
p = Parameter.new(name, type, default)
|
191
|
+
add_parameter(p)
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
# Add self parameter at front.
|
198
|
+
def add_self
|
199
|
+
add_parameter(Parameter.new('self', mod)) if @parameter.empty?
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
def add_parameter(p)
|
204
|
+
if p.restarg
|
205
|
+
raise("Too many restargs") if @restarg
|
206
|
+
@restarg = p
|
207
|
+
@max_args = nil
|
208
|
+
end
|
209
|
+
if p.default
|
210
|
+
(@default ||= [ ]).push(p)
|
211
|
+
end
|
212
|
+
|
213
|
+
p.i = @parameter.size
|
214
|
+
@parameter.push(p)
|
215
|
+
p.signature = self
|
216
|
+
|
217
|
+
unless p.default || p.restarg
|
218
|
+
@min_args = @parameter.size
|
219
|
+
end
|
220
|
+
|
221
|
+
unless @restarg
|
222
|
+
@max_args = @parameter.size
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
|
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
|
+
|
236
|
+
def score(args)
|
237
|
+
|
238
|
+
if @min_args > args.size
|
239
|
+
# Not enough args
|
240
|
+
score = nil
|
241
|
+
elsif @max_args && @max_args < args.size
|
242
|
+
# Too many args?
|
243
|
+
# $stderr.puts "max_args = #{@max_args}, args.size = #{args.size}"
|
244
|
+
score = nil
|
245
|
+
else
|
246
|
+
# Interpret how close the argument type is to the parameter's type.
|
247
|
+
i = -1
|
248
|
+
score = args.collect{|a| parameter_at(i = i + 1).score(a)}
|
249
|
+
|
250
|
+
# Handle score for trailing restargs.
|
251
|
+
if @restarg || @default
|
252
|
+
while (i = i + 1) < @parameter.size
|
253
|
+
# $stderr.puts " Adding score i=#{i}"
|
254
|
+
score << parameter_at(i).score(NilClass)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# If any argument cannot match, avoid this method.
|
259
|
+
score = nil if score.index(nil)
|
260
|
+
end
|
261
|
+
|
262
|
+
# if true || @name =~ /_bar$/
|
263
|
+
# $stderr.puts " Method: score #{self.to_s} #{args.inspect} => #{score.inspect}"
|
264
|
+
# end
|
265
|
+
|
266
|
+
score
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
def parameter_at(i)
|
271
|
+
if i >= @parameter.size && @restarg
|
272
|
+
@restarg
|
273
|
+
else
|
274
|
+
@parameter[i]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
def to_s(name = nil)
|
280
|
+
name ||= @name || '_'
|
281
|
+
p = @parameter.clone
|
282
|
+
rcvr = p.shift
|
283
|
+
"#{rcvr.type.name}##{name}(#{parameter_to_s(p)})"
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
def parameter_to_s(p = nil)
|
288
|
+
p ||= @parameter
|
289
|
+
p.collect{|x| x.to_s}.join(', ')
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
def to_ruby_def(name = nil)
|
294
|
+
name ||= @name || '_'
|
295
|
+
"def #{name}(#{to_ruby_arg})"
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
def to_ruby_signature(name = nil)
|
300
|
+
name ||= @name || '_'
|
301
|
+
p = @parameter.clone
|
302
|
+
rcvr = p.shift
|
303
|
+
m = mod
|
304
|
+
"#{m && m.name}##{name}(#{to_ruby_arg})"
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
def to_ruby_arg
|
309
|
+
x = @parameter.clone
|
310
|
+
x.shift
|
311
|
+
x.collect{|x| x.to_ruby_arg}.join(', ')
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
def inspect
|
316
|
+
to_s
|
317
|
+
end
|
318
|
+
|
319
|
+
end # class
|
320
|
+
end # module
|
321
|
+
|
322
|
+
|