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,2 @@
1
+ This is a Subversion working copy administrative directory.
2
+ Visit http://subversion.tigris.org/ for more information.
File without changes
@@ -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>
@@ -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
+