multimethod 0.0.1
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/.svn/README.txt +2 -0
- data/.svn/empty-file +0 -0
- data/.svn/entries +68 -0
- data/.svn/format +1 -0
- data/.svn/text-base/ChangeLog.svn-base +3 -0
- data/.svn/text-base/Manifest.txt.svn-base +54 -0
- data/.svn/text-base/README.txt.svn-base +40 -0
- data/.svn/text-base/Rakefile.svn-base +132 -0
- data/.svn/text-base/Releases.txt.svn-base +7 -0
- data/ChangeLog +3 -0
- data/Manifest.txt +54 -0
- data/README.txt +40 -0
- data/Rakefile +132 -0
- data/Releases.txt +7 -0
- data/examples/.svn/README.txt +2 -0
- data/examples/.svn/empty-file +0 -0
- data/examples/.svn/entries +22 -0
- data/examples/.svn/format +1 -0
- data/examples/.svn/props/ex1.rb.svn-work +5 -0
- data/examples/ex1.rb +26 -0
- data/lib/.svn/README.txt +2 -0
- data/lib/.svn/empty-file +0 -0
- data/lib/.svn/entries +24 -0
- data/lib/.svn/format +1 -0
- data/lib/.svn/text-base/multimethod.rb.svn-base +8 -0
- data/lib/multimethod.rb +8 -0
- data/lib/multimethod/.svn/README.txt +2 -0
- data/lib/multimethod/.svn/empty-file +0 -0
- data/lib/multimethod/.svn/entries +53 -0
- data/lib/multimethod/.svn/format +1 -0
- data/lib/multimethod/.svn/text-base/core_extensions.rb.svn-base +38 -0
- data/lib/multimethod/.svn/text-base/method.rb.svn-base +232 -0
- data/lib/multimethod/.svn/text-base/multimethod.rb.svn-base +171 -0
- data/lib/multimethod/.svn/text-base/parameter.rb.svn-base +64 -0
- data/lib/multimethod/.svn/text-base/table.rb.svn-base +99 -0
- data/lib/multimethod/core_extensions.rb +38 -0
- data/lib/multimethod/method.rb +232 -0
- data/lib/multimethod/multimethod.rb +171 -0
- data/lib/multimethod/parameter.rb +64 -0
- data/lib/multimethod/table.rb +99 -0
- data/test/.svn/README.txt +2 -0
- data/test/.svn/empty-file +0 -0
- data/test/.svn/entries +53 -0
- data/test/.svn/format +1 -0
- data/test/.svn/text-base/method_test.rb.svn-base +89 -0
- data/test/.svn/text-base/multimethod_test.rb.svn-base +92 -0
- data/test/.svn/text-base/parameter_test.rb.svn-base +31 -0
- data/test/.svn/text-base/test_base.rb.svn-base +25 -0
- data/test/.svn/text-base/usage_test.rb.svn-base +146 -0
- data/test/method_test.rb +89 -0
- data/test/multimethod_test.rb +92 -0
- data/test/parameter_test.rb +31 -0
- data/test/test_base.rb +25 -0
- data/test/usage_test.rb +146 -0
- metadata +108 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
module Multimethod
|
2
|
+
class Multimethod
|
3
|
+
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :method
|
6
|
+
attr_accessor :table
|
7
|
+
|
8
|
+
def initialize(name, *opts)
|
9
|
+
raise NameError, "multimethod name not specified" unless name && name.to_s.size > 0
|
10
|
+
|
11
|
+
@name = name
|
12
|
+
@name_i = 0
|
13
|
+
@method = [ ]
|
14
|
+
@dispatch = { }
|
15
|
+
|
16
|
+
@lookup_method = { }
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def gensym(name = nil)
|
21
|
+
name ||= @name
|
22
|
+
"_multimethod_#{@name_i = @name_i + 1}_#{name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def new_method(mod, *args)
|
27
|
+
m = Method.new(mod, gensym, *args)
|
28
|
+
add_method(m)
|
29
|
+
m
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def add_method(method)
|
34
|
+
# THREAD CRITICAL BEGIN
|
35
|
+
@method.push(method)
|
36
|
+
method.multimethod = self
|
37
|
+
@lookup_method = { } # flush cache
|
38
|
+
# THREAD CRITICAL END
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def dispatch(rcvr, args)
|
43
|
+
apply_method(lookup_method(rcvr, args), rcvr, args)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Interface to Multimethod::Table
|
48
|
+
def apply_method(meth, rcvr, args)
|
49
|
+
unless meth # && false
|
50
|
+
$stderr.puts "Available multimethods for #{rcvr.class.name}##{@name}(#{args}):"
|
51
|
+
$stderr.puts " " + @method.sort{|a,b| a.min_args <=> b.min_args }.collect{|x| x.to_s(name)}.join("\n ")
|
52
|
+
$stderr.puts "\n"
|
53
|
+
end
|
54
|
+
raise NameError, "Cannot find multimethod for #{rcvr.class.name}##{@name}(#{args})" unless meth
|
55
|
+
rcvr.send(meth.name, *args)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def lookup_method(rcvr, args)
|
60
|
+
args = args.clone
|
61
|
+
args.unshift(rcvr)
|
62
|
+
lookup_method_cached_(args)
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def lookup_method_cached_(args)
|
67
|
+
args_type = args.collect{|x| x.class}
|
68
|
+
|
69
|
+
# THREAD CRITICAL BEGIN
|
70
|
+
unless result = @lookup_method[args_type]
|
71
|
+
result = @lookup_method[args_type] =
|
72
|
+
lookup_method_(args_type)
|
73
|
+
end
|
74
|
+
# THREAD CRITICAL END
|
75
|
+
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def lookup_method_(args)
|
81
|
+
scores = score_methods(@method, args)
|
82
|
+
if scores.empty?
|
83
|
+
result = nil
|
84
|
+
else
|
85
|
+
result = scores[0][1]
|
86
|
+
raise("Ambigious method") if scores.select{|x| x[0] == result}.size > 1
|
87
|
+
end
|
88
|
+
|
89
|
+
#if @name.to_s == 'bar'
|
90
|
+
# $stderr.puts "args = " + args.collect{|x| x.class.name + ' ' + x.to_s}.join(", ")
|
91
|
+
# $stderr.puts "scores:\n " + scores.collect{|x| x.inspect}.join("\n ")
|
92
|
+
# end
|
93
|
+
|
94
|
+
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def score_methods(meths, args)
|
99
|
+
scores = meths.collect do |meth|
|
100
|
+
score = meth.score_cached(args)
|
101
|
+
if score
|
102
|
+
score = [ score, meth ]
|
103
|
+
else
|
104
|
+
score = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
score
|
108
|
+
end
|
109
|
+
|
110
|
+
scores.compact!
|
111
|
+
scores.sort!
|
112
|
+
|
113
|
+
# $stderr.puts "score_methods(#{args.inspect}) => \n#{scores.inspect}"
|
114
|
+
|
115
|
+
scores
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def install_dispatch(mod)
|
120
|
+
# THREAD CRITICAL BEGIN
|
121
|
+
unless @dispatch[mod]
|
122
|
+
@dispatch[mod] = true
|
123
|
+
# $stderr.puts "install_dispatch(#{name}) into #{mod}\n";
|
124
|
+
mod.class_eval(body = <<-"end_eval", __FILE__, __LINE__)
|
125
|
+
def #{name}(*args)
|
126
|
+
::#{table.class.name}.instance.dispatch(#{name.inspect}, self, args)
|
127
|
+
end
|
128
|
+
end_eval
|
129
|
+
# $stderr.puts "install_dispatch = #{body}"
|
130
|
+
end
|
131
|
+
# THREAD CRITICAL END
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
##################################################
|
136
|
+
# Support
|
137
|
+
#
|
138
|
+
|
139
|
+
@@name_map = {
|
140
|
+
'@' => 'AT',
|
141
|
+
'=' => 'EQ',
|
142
|
+
'<' => 'LT',
|
143
|
+
'>' => 'GT',
|
144
|
+
'+' => 'ADD',
|
145
|
+
'-' => 'SUB',
|
146
|
+
'*' => 'MUL',
|
147
|
+
'/' => 'DIV',
|
148
|
+
'%' => 'MOD',
|
149
|
+
'^' => 'XOR',
|
150
|
+
'|' => 'OR',
|
151
|
+
'&' => 'AND',
|
152
|
+
'!' => 'NOT',
|
153
|
+
'~' => 'TIL',
|
154
|
+
nil => nil
|
155
|
+
};
|
156
|
+
@@name_map.delete(nil)
|
157
|
+
|
158
|
+
@@name_rx = Regexp.new('(' + @@name_map.keys.collect{|x| Regexp.quote(x)}.join('|') + ')')
|
159
|
+
|
160
|
+
def self.normalize_name(name)
|
161
|
+
name = name.to_s.clone
|
162
|
+
name.sub!(@@name_rx){|x| "_#{@@name_map[x] || '_'}_"}
|
163
|
+
|
164
|
+
name.intern
|
165
|
+
end
|
166
|
+
|
167
|
+
end # class
|
168
|
+
|
169
|
+
end # module
|
170
|
+
|
171
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Multimethod
|
2
|
+
class Parameter
|
3
|
+
RESTARG_SCORE = 9999
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :i
|
7
|
+
attr_accessor :type
|
8
|
+
attr_accessor :default
|
9
|
+
attr_accessor :restarg
|
10
|
+
|
11
|
+
attr_accessor :method
|
12
|
+
|
13
|
+
def initialize(name, type = nil, default = nil, restarg = false)
|
14
|
+
# $stderr.puts "initialize(#{name.inspect}, #{type}, #{restarg.inspect})"
|
15
|
+
name = name.to_s
|
16
|
+
if name.sub!(/^\*/, '')
|
17
|
+
restarg = true
|
18
|
+
end
|
19
|
+
|
20
|
+
name = name.intern unless name.kind_of?(Symbol)
|
21
|
+
@name = name
|
22
|
+
@i = nil
|
23
|
+
@type = type || Kernel
|
24
|
+
@default = default
|
25
|
+
@restarg = restarg
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def score(arg)
|
30
|
+
return RESTARG_SCORE if @restarg
|
31
|
+
score = all_types(arg).index(type_object)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def all_types(arg)
|
36
|
+
arg.ancestors
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def type_object
|
41
|
+
if @type.kind_of?(String)
|
42
|
+
@type = @method.multimethod.table.name_to_object(@type, @method.mod, @method)
|
43
|
+
end
|
44
|
+
@type
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
@restarg ? "*#{@name}" : @name
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def to_s_long
|
54
|
+
"#{@type} #{to_ruby}"
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def to_ruby
|
59
|
+
"#{to_s}#{@default ? ' = ' + @default : ''}"
|
60
|
+
end
|
61
|
+
|
62
|
+
end # class
|
63
|
+
end # module
|
64
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Multimethod
|
2
|
+
class Table
|
3
|
+
|
4
|
+
@@instance = nil
|
5
|
+
def self.instance
|
6
|
+
# THREAD CRITICAL BEGIN
|
7
|
+
@@instance ||= self.new
|
8
|
+
# TRREAD CRITICAL END
|
9
|
+
end
|
10
|
+
|
11
|
+
@@hook_var = :@@__multimethods
|
12
|
+
|
13
|
+
def initialize(*opts)
|
14
|
+
@method = { }
|
15
|
+
|
16
|
+
# Type name lookup cache
|
17
|
+
@name_to_object = { }
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
@@match_def = /^\s*def\s+(\w+)([(](.*)[)])?/
|
22
|
+
# Interface to Multimethod::Module mixin multimethod
|
23
|
+
def install_method(mod, body, file = nil, line = nil)
|
24
|
+
name = nil
|
25
|
+
params = nil
|
26
|
+
|
27
|
+
# Parse first line.
|
28
|
+
if md = body.match(@@match_def)
|
29
|
+
name = md[1]
|
30
|
+
params = md[3] || ''
|
31
|
+
else
|
32
|
+
raise("Cannot determine name(params) from body")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get our Multimethod for this
|
36
|
+
mm = lookup_multimethod(name)
|
37
|
+
mm.install_dispatch(mod)
|
38
|
+
m = mm.new_method(mod, params)
|
39
|
+
|
40
|
+
# Evaluate our method.
|
41
|
+
new_body = body.clone
|
42
|
+
new_body.sub!(@@match_def, m.to_ruby)
|
43
|
+
|
44
|
+
# if m.restarg
|
45
|
+
# $stderr.puts "install_method(#{mod}) => #{m.to_ruby}:\n#{new_body}"
|
46
|
+
# end
|
47
|
+
|
48
|
+
file ||= __FILE__; line ||= __LINE__
|
49
|
+
m.file = file; m.line = line
|
50
|
+
mod.module_eval(new_body, file || __FILE__, line || __LINE__)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def lookup_multimethod(name)
|
55
|
+
name = name.to_s
|
56
|
+
|
57
|
+
# THREAD CRITICAL BEGIN
|
58
|
+
unless mm = @method[name]
|
59
|
+
mm = Multimethod.new(name)
|
60
|
+
mm.table = self
|
61
|
+
@method[name] = mm
|
62
|
+
end
|
63
|
+
# THREAD CRITICAL END
|
64
|
+
|
65
|
+
mm
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Interface to code generated by #install_dispatch.
|
70
|
+
def dispatch(name, rcvr, args)
|
71
|
+
unless mm = @method[name]
|
72
|
+
raise NameError, 'No method for multmethod #{name}' unless mm
|
73
|
+
end
|
74
|
+
mm.dispatch(rcvr, args)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
#################################################
|
79
|
+
# Support
|
80
|
+
#
|
81
|
+
|
82
|
+
def name_to_object(name, scope = nil, method = nil)
|
83
|
+
scope ||= Kernel
|
84
|
+
# THREAD CRITICAL
|
85
|
+
unless x = (@name_to_object[scope] ||= { })[name]
|
86
|
+
# $stderr.puts " name_to_object(#{name.inspect}) in #{scope}"
|
87
|
+
x =
|
88
|
+
@name_to_object[scope][name] =
|
89
|
+
scope.module_eval(name, method ? method.file : __FILE__, method ? method.line : __LINE__)
|
90
|
+
end
|
91
|
+
|
92
|
+
x
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class
|
96
|
+
|
97
|
+
end # module
|
98
|
+
|
99
|
+
|
File without changes
|
data/test/.svn/entries
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<wc-entries
|
3
|
+
xmlns="svn:">
|
4
|
+
<entry
|
5
|
+
committed-rev="3"
|
6
|
+
name=""
|
7
|
+
committed-date="2006-11-21T15:59:22.761488Z"
|
8
|
+
url="svn+ssh://rubyforge.org/var/svn/multimethod/multimethod/trunk/test"
|
9
|
+
last-author="kstephens"
|
10
|
+
kind="dir"
|
11
|
+
repos="svn+ssh://rubyforge.org/var/svn/multimethod"
|
12
|
+
revision="3"/>
|
13
|
+
<entry
|
14
|
+
committed-rev="3"
|
15
|
+
name="method_test.rb"
|
16
|
+
text-time="2006-11-21T05:04:48.000000Z"
|
17
|
+
committed-date="2006-11-21T15:59:22.761488Z"
|
18
|
+
checksum="40b4ca1c1c328339e7380b23522998e5"
|
19
|
+
last-author="kstephens"
|
20
|
+
kind="file"/>
|
21
|
+
<entry
|
22
|
+
committed-rev="3"
|
23
|
+
name="parameter_test.rb"
|
24
|
+
text-time="2006-11-21T07:25:08.000000Z"
|
25
|
+
committed-date="2006-11-21T15:59:22.761488Z"
|
26
|
+
checksum="6893f4ec384c8ed8b5bede4642783d13"
|
27
|
+
last-author="kstephens"
|
28
|
+
kind="file"/>
|
29
|
+
<entry
|
30
|
+
committed-rev="3"
|
31
|
+
name="usage_test.rb"
|
32
|
+
text-time="2006-11-21T06:51:40.000000Z"
|
33
|
+
committed-date="2006-11-21T15:59:22.761488Z"
|
34
|
+
checksum="c3b86cfd548e87f6a3abfba9b6e64c54"
|
35
|
+
last-author="kstephens"
|
36
|
+
kind="file"/>
|
37
|
+
<entry
|
38
|
+
committed-rev="3"
|
39
|
+
name="test_base.rb"
|
40
|
+
text-time="2006-11-20T21:58:18.000000Z"
|
41
|
+
committed-date="2006-11-21T15:59:22.761488Z"
|
42
|
+
checksum="52aa7dfea864715aede8ab3e5aba1ca0"
|
43
|
+
last-author="kstephens"
|
44
|
+
kind="file"/>
|
45
|
+
<entry
|
46
|
+
committed-rev="3"
|
47
|
+
name="multimethod_test.rb"
|
48
|
+
text-time="2006-11-21T07:23:21.000000Z"
|
49
|
+
committed-date="2006-11-21T15:59:22.761488Z"
|
50
|
+
checksum="4de3fbfd3e551eaecc1b9a550b08a80a"
|
51
|
+
last-author="kstephens"
|
52
|
+
kind="file"/>
|
53
|
+
</wc-entries>
|
data/test/.svn/format
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
4
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test_base'
|
2
|
+
|
3
|
+
module Multimethod
|
4
|
+
|
5
|
+
class MethodTest < TestBase
|
6
|
+
|
7
|
+
class A < Object; end
|
8
|
+
class B < A; end
|
9
|
+
class C < Object; end
|
10
|
+
class D < B; end
|
11
|
+
|
12
|
+
def setup
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_scan_parameter
|
17
|
+
assert_not_nil m1 = Method.new(Object, :m, [ A, :a, B, :b, :c, '*d' ])
|
18
|
+
|
19
|
+
assert_equal 5, m1.parameter.size
|
20
|
+
|
21
|
+
i = -1
|
22
|
+
|
23
|
+
i = i + 1
|
24
|
+
assert_equal :self, m1.parameter[i].name
|
25
|
+
assert_equal Object, m1.parameter[i].type
|
26
|
+
assert ! m1.parameter[i].restarg
|
27
|
+
|
28
|
+
i = i + 1
|
29
|
+
assert_equal :a, m1.parameter[i].name
|
30
|
+
assert_equal A, m1.parameter[i].type
|
31
|
+
assert ! m1.parameter[i].restarg
|
32
|
+
|
33
|
+
i = i + 1
|
34
|
+
assert_equal :b, m1.parameter[i].name
|
35
|
+
assert_equal B, m1.parameter[i].type
|
36
|
+
assert ! m1.parameter[i].restarg
|
37
|
+
|
38
|
+
i = i + 1
|
39
|
+
assert_equal :c, m1.parameter[i].name
|
40
|
+
assert_equal Kernel, m1.parameter[i].type
|
41
|
+
assert ! m1.parameter[i].restarg
|
42
|
+
|
43
|
+
i = i + 1
|
44
|
+
assert_equal :d, m1.parameter[i].name
|
45
|
+
assert_equal Kernel, m1.parameter[i].type
|
46
|
+
assert m1.parameter[i].restarg
|
47
|
+
|
48
|
+
m1
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_scan_parameter_string
|
52
|
+
assert_not_nil m1 = Method.new(Object, :m, 'A a, B b, c = nil, *d')
|
53
|
+
|
54
|
+
assert_equal 5, m1.parameter.size
|
55
|
+
|
56
|
+
i = -1
|
57
|
+
|
58
|
+
i = i + 1
|
59
|
+
assert_equal :self, m1.parameter[i].name
|
60
|
+
assert_equal Object, m1.parameter[i].type
|
61
|
+
assert ! m1.parameter[i].restarg
|
62
|
+
|
63
|
+
i = i + 1
|
64
|
+
assert_equal :a, m1.parameter[i].name
|
65
|
+
assert_equal 'A', m1.parameter[i].type
|
66
|
+
assert ! m1.parameter[i].restarg
|
67
|
+
|
68
|
+
i = i + 1
|
69
|
+
assert_equal :b, m1.parameter[i].name
|
70
|
+
assert_equal 'B', m1.parameter[i].type
|
71
|
+
assert ! m1.parameter[i].restarg
|
72
|
+
|
73
|
+
i = i + 1
|
74
|
+
assert_equal :c, m1.parameter[i].name
|
75
|
+
assert_equal Kernel, m1.parameter[i].type
|
76
|
+
assert ! m1.parameter[i].restarg
|
77
|
+
|
78
|
+
i = i + 1
|
79
|
+
assert_equal :d, m1.parameter[i].name
|
80
|
+
assert_equal Kernel, m1.parameter[i].type
|
81
|
+
assert m1.parameter[i].restarg
|
82
|
+
|
83
|
+
m1
|
84
|
+
end
|
85
|
+
|
86
|
+
end # class
|
87
|
+
|
88
|
+
end # module
|
89
|
+
|