protocol 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -87,6 +87,7 @@ module Protocol
87
87
  begin
88
88
  source = IO.readlines(filename)
89
89
  cache[filename] = source
90
+ $DEBUG and warn "#{self.class} just cached #{filename.inspect}."
90
91
  rescue SystemCallError => e
91
92
  $DEBUG and warn "Caught #{e.class}: #{e}"
92
93
  nil
@@ -103,16 +104,18 @@ module Protocol
103
104
  source = source[(lineno - 1)..-1].join
104
105
  current = 0
105
106
  tree = nil
107
+ parser = RubyParser.new
106
108
  while current = source.index('end', current)
107
109
  current += 3
108
110
  begin
109
- tree = RubyParser.new.parse(source[0, current], filename)
111
+ tree = parser.parse(source[0, current], filename)
110
112
  break
111
113
  rescue SyntaxError, Racc::ParseError
112
114
  end
115
+ parser.reset
113
116
  end
114
117
  ary = tree.to_a.flatten
115
- @complex = ary.flatten.any? { |node| [ :call, :fcall, :vcall ].include?(node) }
118
+ @complex = ary.any? { |node| [ :call, :fcall, :vcall ].include?(node) }
116
119
  if ary.index(:yield) and @arg_kinds.last != :block
117
120
  @args.push :'&block'
118
121
  @arg_kinds.push :block
@@ -0,0 +1,57 @@
1
+ module Protocol
2
+ # This class is a proxy that stores postcondition blocks, which are called
3
+ # after the result of the wrapped method was determined.
4
+ class Postcondition
5
+ instance_methods.each do |m|
6
+ m.to_s =~ /\A(__|object_id|instance_eval\z|inspect\z)/ or undef_method m
7
+ end
8
+
9
+ def initialize(object)
10
+ @object = object
11
+ @blocks = []
12
+ end
13
+
14
+ # This is the alternative result "keyword".
15
+ def __result__
16
+ @result
17
+ end
18
+
19
+ # This is the result "keyword" which can be used to query the result of
20
+ # wrapped method in a postcondition clause.
21
+ def result
22
+ if @object.respond_to? :result
23
+ warn "#{@object.class} already defines a result method, "\
24
+ "try __result__ instead"
25
+ @object.__send__(:result)
26
+ else
27
+ @result
28
+ end
29
+ end
30
+
31
+ # This is the "keyword" to be used instead of +self+ to refer to current
32
+ # object.
33
+ def myself
34
+ @object
35
+ end
36
+
37
+ # :stopdoc:
38
+ def __result__=(result)
39
+ @result = result
40
+ end
41
+
42
+ def __check__
43
+ @blocks.all? { |block| instance_eval(&block) }
44
+ end
45
+
46
+ def __add__(block)
47
+ @blocks << block
48
+ self
49
+ end
50
+ # :startdoc:
51
+
52
+ # Send all remaining messages to the object.
53
+ def method_missing(*a, &b)
54
+ @object.__send__(*a, &b)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,291 @@
1
+ module Protocol
2
+ # A ProtocolModule object
3
+ class ProtocolModule < Module
4
+ # Creates an new ProtocolModule instance.
5
+ def initialize(&block)
6
+ @descriptor = Descriptor.new(self)
7
+ @mode = :error
8
+ module_eval(&block)
9
+ end
10
+
11
+ # The current check mode :none, :warning, or :error (the default).
12
+ attr_reader :mode
13
+
14
+ # Returns all the protocol descriptions to check against as an Array.
15
+ def descriptors
16
+ descriptors = []
17
+ protocols.each do |a|
18
+ descriptors << a.instance_variable_get(:@descriptor)
19
+ end
20
+ descriptors
21
+ end
22
+
23
+ # Return self and all protocols included into self.
24
+ def protocols
25
+ ancestors.select { |modul| modul.is_a? ProtocolModule }
26
+ end
27
+
28
+ # Concatenates the protocol as Ruby code to the +result+ string and return
29
+ # it. At the moment this method only supports method signatures with
30
+ # generic argument names.
31
+ def to_ruby(result = '')
32
+ result << "#{name} = Protocol do"
33
+ first = true
34
+ if messages.empty?
35
+ result << "\n"
36
+ else
37
+ messages.each do |m|
38
+ result << "\n"
39
+ m.to_ruby(result)
40
+ end
41
+ end
42
+ result << "end\n"
43
+ end
44
+
45
+ # Returns all messages this protocol (plus the included protocols) consists
46
+ # of in alphabetic order. This method caches the computed result array. You
47
+ # have to call #reset_messages, if you want to recompute the array in the
48
+ # next call to #messages.
49
+ def messages
50
+ result = []
51
+ seen = {}
52
+ descriptors.each do |d|
53
+ dm = d.messages
54
+ dm.delete_if do |m|
55
+ delete = seen[m.name]
56
+ seen[m.name] = true
57
+ delete
58
+ end
59
+ result.concat dm
60
+ end
61
+ result.sort!
62
+ end
63
+
64
+ alias to_a messages
65
+
66
+ # Reset the cached message array. Call this if you want to change the
67
+ # protocol dynamically after it was already used (= the #messages method
68
+ # was called).
69
+ def reset_messages
70
+ @messages = nil
71
+ self
72
+ end
73
+
74
+ # Returns true if it is required to understand the
75
+ def understand?(name, arity = nil)
76
+ name = name.to_s
77
+ !!find { |m| m.name == name && (!arity || m.arity == arity) }
78
+ end
79
+
80
+ # Return the Message object named +name+ or nil, if it doesn't exist.
81
+ def [](name)
82
+ name = name.to_s
83
+ find { |m| m.name == name }
84
+ end
85
+
86
+ # Return all message whose names matches pattern.
87
+ def grep(pattern)
88
+ select { |m| pattern === m.name }
89
+ end
90
+
91
+ # Iterate over all messages and yield to all of them.
92
+ def each_message(&block) # :yields: message
93
+ messages.each(&block)
94
+ self
95
+ end
96
+ alias each each_message
97
+
98
+ include Enumerable
99
+
100
+ # Returns a string representation of this protocol, that consists of the
101
+ # understood messages. This protocol
102
+ #
103
+ # FooProtocol = Protocol do
104
+ # def bar(x, y, &b) end
105
+ # def baz(x, y, z) end
106
+ # def foo(*rest) end
107
+ # end
108
+ #
109
+ # returns this string:
110
+ #
111
+ # FooProtocol#bar(2&), FooProtocol#baz(3), FooProtocol#foo(-1)
112
+ def to_s
113
+ messages * ', '
114
+ end
115
+
116
+ # Returns a short string representation of this protocol, that consists of
117
+ # the understood messages. This protocol
118
+ #
119
+ # FooProtocol = Protocol do
120
+ # def bar(x, y, &b) end
121
+ # def baz(x, y, z) end
122
+ # def foo(*rest) end
123
+ # end
124
+ #
125
+ # returns this string:
126
+ #
127
+ # #<FooProtocol: bar(2&), baz(3), foo(-1)>
128
+ def inspect
129
+ "#<#{name}: #{messages.map { |m| m.shortcut } * ', '}>"
130
+ end
131
+
132
+ # Check the conformity of +object+ recursively. This method returns either
133
+ # false OR true, if +mode+ is :none or :warning, or raises an
134
+ # CheckFailed, if +mode+ was :error.
135
+ def check(object, mode = @mode)
136
+ checked = {}
137
+ result = true
138
+ errors = CheckFailed.new
139
+ each do |message|
140
+ begin
141
+ message.check(object, checked)
142
+ rescue CheckError => e
143
+ case mode
144
+ when :error
145
+ errors << e
146
+ when :warning
147
+ warn e.to_s
148
+ result = false
149
+ when :none
150
+ result = false
151
+ end
152
+ end
153
+ end
154
+ raise errors unless errors.empty?
155
+ result
156
+ end
157
+
158
+ alias =~ check
159
+
160
+ # Return all messages for whick a check failed.
161
+ def check_failures(object)
162
+ check object
163
+ rescue CheckFailed => e
164
+ return e.errors.map { |e| e.protocol_message }
165
+ end
166
+
167
+ # This callback is called, when a module, that was extended with Protocol,
168
+ # is included (via Modul#include/via Class#conform_to) into some other
169
+ # module/class.
170
+ # If +modul+ is a Class, all protocol descriptions of the inheritance tree
171
+ # are collected and the given class is checked for conformance to the
172
+ # protocol. +modul+ isn't a Class and only a Module, it is extended with
173
+ # the Protocol
174
+ # module.
175
+ def included(modul)
176
+ super
177
+ if modul.is_a? Class and @mode == :error or @mode == :warning
178
+ $DEBUG and warn "#{name} is checking class #{modul}"
179
+ check modul
180
+ end
181
+ end
182
+
183
+ def extend_object(object)
184
+ result = super
185
+ if @mode == :error or @mode == :warning
186
+ $DEBUG and warn "#{name} is checking class #{object}"
187
+ check object
188
+ end
189
+ result
190
+ end
191
+
192
+ # Sets the check mode to +id+. +id+ should be one of :none, :warning, or
193
+ # :error. The mode to use while doing a conformity check is always the root
194
+ # module, that is, the modes of the included modules aren't important for
195
+ # the check.
196
+ def check_failure(mode)
197
+ CHECK_MODES.include?(mode) or
198
+ raise ArgumentError, "illegal check mode #{mode}"
199
+ @mode = mode
200
+ end
201
+
202
+ # This method defines one of the messages, the protocol in question
203
+ # consists of: The messages which the class, that conforms to this
204
+ # protocol, should understand and respond to. An example shows best
205
+ # which +message+descriptions_ are allowed:
206
+ #
207
+ # MyProtocol = Protocol do
208
+ # understand :bar # conforming class must respond to :bar
209
+ # understand :baz, 3 # c. c. must respond to :baz with 3 args.
210
+ # understand :foo, -1 # c. c. must respond to :foo, any number of args.
211
+ # understand :quux, 0, true # c. c. must respond to :quux, no args + block.
212
+ # understand :quux1, 1, true # c. c. must respond to :quux, 1 arg + block.
213
+ # end
214
+ def understand(methodname, arity = nil, block_expected = false)
215
+ m = Message.new(self, methodname.to_s, arity, block_expected)
216
+ @descriptor.add_message(m)
217
+ self
218
+ end
219
+
220
+ def parse_instance_method_signature(modul, methodname)
221
+ methodname = methodname.to_s
222
+ method = modul.instance_method(methodname)
223
+ real_module = Utilities.find_method_module(methodname, modul.ancestors)
224
+ parser = MethodParser.new(real_module, methodname)
225
+ Message.new(self, methodname, method.arity, parser.block_arg?)
226
+ end
227
+ private :parse_instance_method_signature
228
+
229
+ # Inherit a method signature from an instance method named +methodname+ of
230
+ # +modul+. This means that this protocol should understand these instance
231
+ # methods with their arity and block expectation. Note that automatic
232
+ # detection of blocks does not work for Ruby methods defined in C. You can
233
+ # set the +block_expected+ argument if you want to do this manually.
234
+ def inherit(modul, methodname, block_expected = nil)
235
+ Module === modul or
236
+ raise TypeError, "expected Module not #{modul.class} as modul argument"
237
+ methodnames = methodname.respond_to?(:to_ary) ?
238
+ methodname.to_ary :
239
+ [ methodname ]
240
+ methodnames.each do |methodname|
241
+ m = parse_instance_method_signature(modul, methodname)
242
+ block_expected and m.block_expected = block_expected
243
+ @descriptor.add_message m
244
+ end
245
+ self
246
+ end
247
+
248
+ # Switch to implementation mode. Defined methods are added to the
249
+ # ProtocolModule as instance methods.
250
+ def implementation
251
+ @implementation = true
252
+ end
253
+
254
+ # Return true, if the ProtocolModule is currently in implementation mode.
255
+ # Otherwise return false.
256
+ def implementation?
257
+ !!@implementation
258
+ end
259
+
260
+ # Switch to specification mode. Defined methods are added to the protocol
261
+ # description in order to be checked against in later conformance tests.
262
+ def specification
263
+ @implementation = false
264
+ end
265
+
266
+ # Return true, if the ProtocolModule is currently in specification mode.
267
+ # Otherwise return false.
268
+ def specification?
269
+ !@implementation
270
+ end
271
+
272
+ # Capture all added methods and either leave the implementation in place or
273
+ # add them to the protocol description.
274
+ def method_added(methodname)
275
+ methodname = methodname.to_s
276
+ if specification? and methodname !~ /^__protocol_check_/
277
+ protocol_check = instance_method(methodname)
278
+ parser = MethodParser.new(self, methodname)
279
+ if parser.complex?
280
+ define_method("__protocol_check_#{methodname}", protocol_check)
281
+ understand methodname, protocol_check.arity, parser.block_arg?
282
+ else
283
+ understand methodname, protocol_check.arity, parser.block_arg?
284
+ end
285
+ remove_method methodname
286
+ else
287
+ super
288
+ end
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,21 @@
1
+ module Protocol
2
+ # A module for some Utility methods.
3
+ module Utilities
4
+ module_function
5
+
6
+ # This Method tries to find the first module that implements the method
7
+ # named +methodname+ in the array of +ancestors+. If this fails nil is
8
+ # returned.
9
+ def find_method_module(methodname, ancestors)
10
+ methodname = methodname.to_s
11
+ ancestors.each do |a|
12
+ begin
13
+ a.instance_method(methodname)
14
+ return a
15
+ rescue NameError
16
+ end
17
+ end
18
+ nil
19
+ end
20
+ end
21
+ end
@@ -1,6 +1,6 @@
1
1
  module Protocol
2
2
  # Protocol version
3
- VERSION = '0.9.0'
3
+ VERSION = '1.0.0'
4
4
  VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -0,0 +1,41 @@
1
+ module Protocol
2
+ class ::Object
3
+ # Returns true if this object conforms to +protocol+, otherwise false.
4
+ #
5
+ # This is especially useful, if check_failure in the protocol is set to
6
+ # :none or :warning, and conformance of a class to a protocol should be
7
+ # checked later in runtime.
8
+ def conform_to?(protocol)
9
+ protocol.check(self, :none)
10
+ end
11
+
12
+ def conform_to!(protocol)
13
+ extend(protocol)
14
+ end
15
+
16
+ # Define a protocol configured by +block+. Look at the methods of
17
+ # ProtocolModule to get an idea on how to do that.
18
+ def Protocol(&block)
19
+ ProtocolModule.new(&block)
20
+ end
21
+
22
+ alias protocol Protocol
23
+ end
24
+
25
+ class ::Class
26
+ # This method should be called at the end of a class definition, that is,
27
+ # after all methods have been added to the class. The conformance to the
28
+ # protocol of the class given as the argument is checked. See
29
+ # Protocol::CHECK_MODES for the consequences of failure of this check.
30
+ alias conform_to include
31
+
32
+ # Returns true if this class conforms to +protocol+, otherwise false.
33
+ #
34
+ # This is especially useful, if check_failure in the protocol is set to
35
+ # :none or :warning, and conformance of a class to a protocol should be
36
+ # checked later in runtime.
37
+ def conform_to?(protocol)
38
+ protocol.check(self, :none)
39
+ end
40
+ end
41
+ end