multimethod 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.svn/README.txt +2 -0
  2. data/.svn/empty-file +0 -0
  3. data/.svn/entries +68 -0
  4. data/.svn/format +1 -0
  5. data/.svn/text-base/ChangeLog.svn-base +3 -0
  6. data/.svn/text-base/Manifest.txt.svn-base +54 -0
  7. data/.svn/text-base/README.txt.svn-base +40 -0
  8. data/.svn/text-base/Rakefile.svn-base +132 -0
  9. data/.svn/text-base/Releases.txt.svn-base +7 -0
  10. data/ChangeLog +3 -0
  11. data/Manifest.txt +54 -0
  12. data/README.txt +40 -0
  13. data/Rakefile +132 -0
  14. data/Releases.txt +7 -0
  15. data/examples/.svn/README.txt +2 -0
  16. data/examples/.svn/empty-file +0 -0
  17. data/examples/.svn/entries +22 -0
  18. data/examples/.svn/format +1 -0
  19. data/examples/.svn/props/ex1.rb.svn-work +5 -0
  20. data/examples/ex1.rb +26 -0
  21. data/lib/.svn/README.txt +2 -0
  22. data/lib/.svn/empty-file +0 -0
  23. data/lib/.svn/entries +24 -0
  24. data/lib/.svn/format +1 -0
  25. data/lib/.svn/text-base/multimethod.rb.svn-base +8 -0
  26. data/lib/multimethod.rb +8 -0
  27. data/lib/multimethod/.svn/README.txt +2 -0
  28. data/lib/multimethod/.svn/empty-file +0 -0
  29. data/lib/multimethod/.svn/entries +53 -0
  30. data/lib/multimethod/.svn/format +1 -0
  31. data/lib/multimethod/.svn/text-base/core_extensions.rb.svn-base +38 -0
  32. data/lib/multimethod/.svn/text-base/method.rb.svn-base +232 -0
  33. data/lib/multimethod/.svn/text-base/multimethod.rb.svn-base +171 -0
  34. data/lib/multimethod/.svn/text-base/parameter.rb.svn-base +64 -0
  35. data/lib/multimethod/.svn/text-base/table.rb.svn-base +99 -0
  36. data/lib/multimethod/core_extensions.rb +38 -0
  37. data/lib/multimethod/method.rb +232 -0
  38. data/lib/multimethod/multimethod.rb +171 -0
  39. data/lib/multimethod/parameter.rb +64 -0
  40. data/lib/multimethod/table.rb +99 -0
  41. data/test/.svn/README.txt +2 -0
  42. data/test/.svn/empty-file +0 -0
  43. data/test/.svn/entries +53 -0
  44. data/test/.svn/format +1 -0
  45. data/test/.svn/text-base/method_test.rb.svn-base +89 -0
  46. data/test/.svn/text-base/multimethod_test.rb.svn-base +92 -0
  47. data/test/.svn/text-base/parameter_test.rb.svn-base +31 -0
  48. data/test/.svn/text-base/test_base.rb.svn-base +25 -0
  49. data/test/.svn/text-base/usage_test.rb.svn-base +146 -0
  50. data/test/method_test.rb +89 -0
  51. data/test/multimethod_test.rb +92 -0
  52. data/test/parameter_test.rb +31 -0
  53. data/test/test_base.rb +25 -0
  54. data/test/usage_test.rb +146 -0
  55. 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
+
@@ -0,0 +1,38 @@
1
+ module Multimethod
2
+
3
+ module ObjectExtension
4
+ def self.append_features(base) # :nodoc:
5
+ # puts "append_features{#{base}}"
6
+ super
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def multimethod(body, file = nil, line = nil)
12
+ unless file && line
13
+ fileline = caller(1)[0]
14
+ if fileline && md = /^(.*):(\d+)$/.match(fileline)
15
+ file, line = md[1], md[2].to_i
16
+
17
+ newlines = 0
18
+ body.gsub(/\n/s){|x| newlines = newlines + 1}
19
+ line -= newlines
20
+ end
21
+
22
+ # $stderr.puts "file = #{file.inspect}, line = #{line.inspect}"
23
+ end
24
+
25
+ ::Multimethod::Table.instance.install_method(self, body, file, line)
26
+ end
27
+ end
28
+ end # class
29
+ end # module
30
+
31
+
32
+ # Add to Module
33
+ Object.class_eval do
34
+ include Multimethod::ObjectExtension
35
+ end
36
+
37
+
38
+
@@ -0,0 +1,232 @@
1
+ module Multimethod
2
+
3
+ class Method
4
+ attr_accessor :mod
5
+ attr_accessor :name
6
+ attr_accessor :parameter
7
+
8
+ attr_accessor :min_args
9
+ attr_accessor :max_args
10
+ attr_accessor :restarg
11
+ attr_accessor :default
12
+
13
+ attr_accessor :multimethod
14
+ attr_accessor :file
15
+ attr_accessor :line
16
+
17
+ def initialize(mod, name, params)
18
+ raise NameError, "multimethod method name not specified" unless name && name.to_s.size > 0
19
+
20
+ name = Multimethod.normalize_name(name)
21
+
22
+ @mod = mod
23
+ @name = name
24
+ @parameter = [ ]
25
+ @min_args = 0
26
+ @max_args = 0
27
+ @restarg = nil
28
+ @default = nil
29
+
30
+ @score = { }
31
+
32
+ # Add self parameter at front.
33
+ add_parameter(Parameter.new('self', mod))
34
+
35
+ # Handle other parameters.
36
+ case params
37
+ when Array
38
+ scan_parameters(params)
39
+ when String
40
+ scan_parameters_string(params)
41
+ end
42
+ end
43
+
44
+
45
+ # For sort
46
+ def <=>(x)
47
+ 0
48
+ end
49
+
50
+
51
+ def scan_parameters_string(params)
52
+
53
+ #$stderr.puts "scan_parameters_string(#{params.inspect})"
54
+
55
+ str = params.clone
56
+
57
+ until str.empty?
58
+ name = nil
59
+ type = nil
60
+ default = nil
61
+
62
+ str.sub!(/^\s+/, '')
63
+
64
+ # $stderr.puts " str=#{str.inspect}"
65
+
66
+ if md = /^(\w+(::\w+)*)\s+(\w+)/i.match(str)
67
+ str = md.post_match
68
+ type = md[1]
69
+ name = md[3]
70
+ elsif md = /^(\*?\w+)/i.match(str)
71
+ str = md.post_match
72
+ type = nil
73
+ name = md[1]
74
+ else
75
+ raise NameError, "Syntax error in multimethod parameters: #{params.inspect} before #{str.inspect}"
76
+ end
77
+
78
+ if md = /^\s*=\s*([^,]+)/.match(str)
79
+ str = md.post_match
80
+ default = md[1]
81
+ end
82
+
83
+
84
+ str.sub!(/^\s+/, '')
85
+ if ! str.empty?
86
+ if md = /^,/.match(str)
87
+ str = md.post_match
88
+ else
89
+ raise NameError, "Syntax error in multimethod parameters: expected ',' before #{str.inspect}"
90
+ end
91
+ end
92
+
93
+ p = Parameter.new(name, type, default)
94
+ add_parameter(p)
95
+ end
96
+ end
97
+
98
+
99
+ def scan_parameters(params)
100
+ until params.empty?
101
+ name = nil
102
+ type = nil
103
+ restarg = false
104
+ default = nil
105
+
106
+ if x = params.shift
107
+ case x
108
+ when Class
109
+ type = x
110
+ else
111
+ name = x
112
+ end
113
+ end
114
+
115
+ if ! name && (x = params.shift)
116
+ name = x
117
+ end
118
+
119
+ raise("Parameter name expected, found #{name.inspect}") unless name.kind_of?(String) || name.kind_of?(Symbol)
120
+ raise("Parameter type expected, found #{type.inspect}") unless type.kind_of?(Module) || type.nil?
121
+
122
+ p = Parameter.new(name, type, default)
123
+ add_parameter(p)
124
+ end
125
+
126
+ end
127
+
128
+ def add_parameter(p)
129
+ if p.restarg
130
+ raise("Too many restargs") if @restarg
131
+ @restarg = p
132
+ @max_args = nil
133
+ end
134
+ if p.default
135
+ (@default ||= [ ]).push(p)
136
+ end
137
+
138
+ p.i = @parameter.size
139
+ @parameter.push(p)
140
+ p.method = self
141
+
142
+ unless p.default || p.restarg
143
+ @min_args = @parameter.size
144
+ end
145
+
146
+ unless @restarg
147
+ @max_args = @parameter.size
148
+ end
149
+ end
150
+
151
+
152
+ def score_cached(args)
153
+ unless x = @score[args]
154
+ x = @score[args] =
155
+ score(args)
156
+ end
157
+ x
158
+ end
159
+
160
+
161
+ def score(args)
162
+
163
+ if @min_args > args.size
164
+ # Not enough args
165
+ score = nil
166
+ elsif @max_args && @max_args < args.size
167
+ # Too many args?
168
+ # $stderr.puts "max_args = #{@max_args}, args.size = #{args.size}"
169
+ score = nil
170
+ else
171
+ # Interpret how close the argument type is to the parameter's type.
172
+ i = -1
173
+ score = args.collect{|a| parameter_at(i = i + 1).score(a)}
174
+
175
+ # Handle score for trailing restargs.
176
+ if @restarg || @default
177
+ while (i = i + 1) < @parameter.size
178
+ # $stderr.puts " Adding score i=#{i}"
179
+ score << parameter_at(i).score(NilClass)
180
+ end
181
+ end
182
+
183
+ # If any argument cannot match, avoid this method.
184
+ score = nil if score.index(nil)
185
+ end
186
+
187
+ # if true || @name =~ /_bar$/
188
+ # $stderr.puts " Method: score #{self.to_s} #{args.inspect} => #{score.inspect}"
189
+ # end
190
+
191
+ score
192
+ end
193
+
194
+
195
+ def parameter_at(i)
196
+ if i >= @parameter.size && @restarg
197
+ @restarg
198
+ else
199
+ @parameter[i]
200
+ end
201
+ end
202
+
203
+
204
+ def parameter_to_s(p = nil)
205
+ p ||= @parameter
206
+ p.collect{|x| x.to_s_long}.join(', ')
207
+ end
208
+
209
+ def to_s(name = nil)
210
+ name ||= @name
211
+ p = @parameter.clone
212
+ rcvr = p.shift
213
+ "#{rcvr.type.name}##{name}(#{parameter_to_s(p)})"
214
+ end
215
+
216
+ def to_ruby
217
+ "def #{name}(#{to_s_arg})"
218
+ end
219
+
220
+ def to_s_arg
221
+ x = @parameter.clone
222
+ x.shift
223
+ x.collect{|x| x.to_ruby}.join(', ')
224
+ end
225
+
226
+ def inspect
227
+ to_s
228
+ end
229
+ end # class
230
+ end # module
231
+
232
+