protocol 0.9.0 → 1.0.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.
@@ -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