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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +11 -0
- data/Gemfile +7 -0
- data/{doc-main.txt → README.rdoc} +12 -12
- data/Rakefile +29 -89
- data/VERSION +1 -1
- data/benchmarks/data/.keep +0 -0
- data/benchmarks/method_parser.rb +3 -0
- data/examples/assignments.rb +59 -0
- data/examples/game.rb +1 -1
- data/examples/hello_world_patternitis.rb +1 -1
- data/examples/locking.rb +1 -1
- data/lib/protocol/descriptor.rb +34 -0
- data/lib/protocol/errors.rb +95 -0
- data/lib/protocol/message.rb +219 -0
- data/lib/protocol/method_parser/ruby_parser.rb +5 -2
- data/lib/protocol/post_condition.rb +57 -0
- data/lib/protocol/protocol_module.rb +291 -0
- data/lib/protocol/utilities.rb +21 -0
- data/lib/protocol/version.rb +1 -1
- data/lib/protocol/xt.rb +41 -0
- data/lib/protocol.rb +8 -741
- data/protocol.gemspec +32 -22
- data/tests/{test_protocol_method_parser.rb → protocol_method_parser_test.rb} +2 -3
- data/tests/{test_protocol.rb → protocol_test.rb} +106 -107
- data/tests/test_helper.rb +8 -0
- metadata +105 -65
- data/lib/protocol/method_parser/parse_tree.rb +0 -124
- data/make_doc.rb +0 -5
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cd98524da174341f1dc25b039f03b5ad05adfdc8
|
4
|
+
data.tar.gz: b94806b796960a8413af5b89dd0d6d150db9e174
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 61bc4d85d2fc9f9e08f66eaed176f890ba848cbdd5197708abc86d15992321dafe651d3c007e09e497b600dcfdf54f0585f44a33e22c71319c9aa5857a19592a
|
7
|
+
data.tar.gz: c78f6dde5b37c2fe123b19cc799c64492f50e9d889b370640bc60c1462c4fb5df49f815a9b015458b69c938d130870a3551fc28d42db7b6664be9c4de65f2781
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -1,4 +1,4 @@
|
|
1
|
-
== Protocol - Method Protocol Specifications in Ruby
|
1
|
+
== Protocol - Method Protocol Specifications in Ruby
|
2
2
|
|
3
3
|
=== Author
|
4
4
|
|
@@ -57,11 +57,11 @@ An example of a conforming class is the class +Ary+:
|
|
57
57
|
def initialize
|
58
58
|
@ary = [1, 2, 3]
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
def each(&block)
|
62
62
|
@ary.each(&block)
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
conform_to Enumerating
|
66
66
|
end
|
67
67
|
|
@@ -84,13 +84,13 @@ like this:
|
|
84
84
|
Locking = Protocol do
|
85
85
|
specification # not necessary, because Protocol defaults to specification
|
86
86
|
# mode already
|
87
|
-
|
87
|
+
|
88
88
|
def lock() end
|
89
|
-
|
89
|
+
|
90
90
|
def unlock() end
|
91
|
-
|
91
|
+
|
92
92
|
implementation
|
93
|
-
|
93
|
+
|
94
94
|
def synchronize
|
95
95
|
lock
|
96
96
|
begin
|
@@ -134,7 +134,7 @@ implemtented methods, to make block based locking possbile:
|
|
134
134
|
mutex.synchronize do
|
135
135
|
puts "Synchronized with '#{file.path}'."
|
136
136
|
end
|
137
|
-
|
137
|
+
|
138
138
|
Now it's easy to swap the implementation to a memory based mutex
|
139
139
|
implementation instead:
|
140
140
|
|
@@ -198,15 +198,15 @@ shows how:
|
|
198
198
|
postcondition { top === x }
|
199
199
|
postcondition { result === myself }
|
200
200
|
end
|
201
|
-
|
201
|
+
|
202
202
|
def top() end
|
203
|
-
|
203
|
+
|
204
204
|
def size() end
|
205
|
-
|
205
|
+
|
206
206
|
def empty?()
|
207
207
|
postcondition { size === 0 ? result : !result }
|
208
208
|
end
|
209
|
-
|
209
|
+
|
210
210
|
def pop()
|
211
211
|
s = size
|
212
212
|
precondition { not empty? }
|
data/Rakefile
CHANGED
@@ -1,101 +1,41 @@
|
|
1
|
-
|
2
|
-
require 'rake/gempackagetask'
|
3
|
-
rescue LoadError
|
4
|
-
end
|
5
|
-
require 'rake/clean'
|
6
|
-
require 'rbconfig'
|
7
|
-
include Config
|
8
|
-
|
9
|
-
PKG_NAME = 'protocol'
|
10
|
-
PKG_VERSION = File.read('VERSION').chomp
|
11
|
-
PKG_FILES = FileList['**/*'].exclude(/(CVS|\.svn|pkg|coverage)/)
|
12
|
-
CLEAN.include 'coverage', 'doc', Dir['benchmarks/data/*.*']
|
13
|
-
|
14
|
-
desc "Installing library"
|
15
|
-
task :install do
|
16
|
-
ruby 'install.rb'
|
17
|
-
end
|
18
|
-
|
19
|
-
desc "Creating documentation"
|
20
|
-
task :doc do
|
21
|
-
ruby 'make_doc.rb'
|
22
|
-
end
|
1
|
+
# vim: set filetype=ruby et sw=2 ts=2:
|
23
2
|
|
24
|
-
|
25
|
-
task :test do
|
26
|
-
ruby '-Ilib tests/test_protocol.rb'
|
27
|
-
end
|
28
|
-
|
29
|
-
desc "Testing library (coverage)"
|
30
|
-
task :coverage do
|
31
|
-
sh 'rcov -Ilib tests/test_protocol.rb'
|
32
|
-
end
|
3
|
+
require 'gem_hadar'
|
33
4
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
s.summary = 'Method Protocols for Ruby Classes'
|
42
|
-
s.description = <<EOT
|
5
|
+
GemHadar do
|
6
|
+
name 'protocol'
|
7
|
+
author 'Florian Frank'
|
8
|
+
email 'flori@ping.de'
|
9
|
+
homepage "http://flori.github.com/#{name}"
|
10
|
+
summary 'Method Protocols for Ruby Classes'
|
11
|
+
description <<EOT
|
43
12
|
This library offers an implementation of protocols against which you can check
|
44
13
|
the conformity of your classes or instances of your classes. They are a bit
|
45
14
|
like Java Interfaces, but as mixin modules they can also contain already
|
46
15
|
implemented methods. Additionaly you can define preconditions/postconditions
|
47
16
|
for methods specified in a protocol.
|
48
17
|
EOT
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
GEM
|
65
|
-
|
66
|
-
desc 'Create a gemspec file'
|
67
|
-
task :gemspec do
|
68
|
-
File.open("#{PKG_NAME}.gemspec", 'w') do |f|
|
69
|
-
f.puts spec_src
|
18
|
+
test_dir 'tests'
|
19
|
+
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc', '.AppleDouble'
|
20
|
+
readme 'README.rdoc'
|
21
|
+
dependency 'ruby_parser', '~> 3.0'
|
22
|
+
development_dependency 'simplecov'
|
23
|
+
|
24
|
+
install_library do
|
25
|
+
file = 'lib/protocol.rb'
|
26
|
+
dest = CONFIG["sitelibdir"]
|
27
|
+
install(file, dest)
|
28
|
+
|
29
|
+
dest = File.join(CONFIG["sitelibdir"], 'protocol')
|
30
|
+
mkdir_p dest
|
31
|
+
for file in Dir['lib/protocol/*.rb']
|
32
|
+
install(file, dest)
|
70
33
|
end
|
71
|
-
end
|
72
|
-
|
73
|
-
spec = eval(spec_src)
|
74
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
75
|
-
pkg.need_tar = true
|
76
|
-
pkg.package_files += PKG_FILES
|
77
|
-
end
|
78
|
-
end
|
79
34
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
module Protocol
|
86
|
-
# Protocol version
|
87
|
-
VERSION = '#{PKG_VERSION}'
|
88
|
-
VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
|
89
|
-
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
90
|
-
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
91
|
-
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
92
|
-
end
|
93
|
-
EOT
|
35
|
+
dest = File.join(CONFIG["sitelibdir"], 'protocol', 'method_parser')
|
36
|
+
mkdir_p dest
|
37
|
+
for file in Dir['lib/protocol/method_parser/*.rb']
|
38
|
+
install(file, dest)
|
39
|
+
end
|
94
40
|
end
|
95
41
|
end
|
96
|
-
|
97
|
-
desc "Default task"
|
98
|
-
task :default => [ :version, :gemspec, :test ]
|
99
|
-
|
100
|
-
desc "Prepare a release"
|
101
|
-
task :release => [ :clean, :version, :gemspec, :package ]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
File without changes
|
data/benchmarks/method_parser.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'protocol'
|
4
|
+
|
5
|
+
Assignable = Protocol do
|
6
|
+
def assign_to(assignee)
|
7
|
+
Assignee =~ assignee
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Assignee = Protocol do
|
12
|
+
end
|
13
|
+
|
14
|
+
Assignments = Protocol do
|
15
|
+
implementation
|
16
|
+
|
17
|
+
def assignments
|
18
|
+
@assignable ||= []
|
19
|
+
end
|
20
|
+
|
21
|
+
def add(assignable)
|
22
|
+
Assignable =~ assignable
|
23
|
+
assignments << assignable
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def assign(assignable, assignee)
|
28
|
+
Assignable =~ assignable
|
29
|
+
Assignee =~ assignee
|
30
|
+
assignable.assign_to(assignee)
|
31
|
+
add(assignable)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Task
|
36
|
+
def assign_to(assignee)
|
37
|
+
@assignee = assignee
|
38
|
+
end
|
39
|
+
|
40
|
+
conform_to Assignable
|
41
|
+
end
|
42
|
+
|
43
|
+
class Project
|
44
|
+
conform_to Assignments
|
45
|
+
conform_to Assignee
|
46
|
+
end
|
47
|
+
|
48
|
+
class User
|
49
|
+
conform_to Assignments
|
50
|
+
conform_to Assignee
|
51
|
+
end
|
52
|
+
|
53
|
+
p Project.new.assign(Task.new, User.new)
|
54
|
+
p Project.new.assign(Task.new, Object.new)
|
55
|
+
begin
|
56
|
+
Project.new.assign(Object.new, Task.new)
|
57
|
+
rescue Protocol::CheckError => e
|
58
|
+
p e
|
59
|
+
end
|
data/examples/game.rb
CHANGED
@@ -2,7 +2,7 @@ require 'protocol'
|
|
2
2
|
|
3
3
|
# Small example of the template method pattern with Protocol. (I think, it was
|
4
4
|
# inspired by this wikipedia article:
|
5
|
-
# http://en.wikipedia.org/wiki/Template_method_pattern
|
5
|
+
# http://en.wikipedia.org/wiki/Template_method_pattern
|
6
6
|
Gaming = Protocol do
|
7
7
|
# defaults to specification mode
|
8
8
|
|
data/examples/locking.rb
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Protocol
|
2
|
+
# This class encapsulates the protocol description, to check the classes
|
3
|
+
# against, if the Class#conform_to method is called with the protocol constant
|
4
|
+
# as an argument.
|
5
|
+
class Descriptor
|
6
|
+
# Creates a new Protocol::Descriptor object.
|
7
|
+
def initialize(protocol)
|
8
|
+
@protocol = protocol
|
9
|
+
@messages = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Addes a new Protocol::Message instance to this Protocol::Descriptor
|
13
|
+
# object.
|
14
|
+
def add_message(message)
|
15
|
+
@messages.key?(message.name) and raise SpecificationError,
|
16
|
+
"A message named #{message.name} was already defined in #@protocol"
|
17
|
+
@messages[message.name] = message
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return all the messages stored in this Descriptor instance.
|
21
|
+
def messages
|
22
|
+
@messages.values
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a string representation of this Protocol::Descriptor object.
|
26
|
+
def inspect
|
27
|
+
"#<#{self.class}(#@protocol)>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
messages * ', '
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Protocol
|
2
|
+
# The base class for protocol errors.
|
3
|
+
class ProtocolError < StandardError; end
|
4
|
+
|
5
|
+
# This exception is raise if an error was encountered while defining a
|
6
|
+
# protocol specification.
|
7
|
+
class SpecificationError < ProtocolError; end
|
8
|
+
|
9
|
+
# Marker module for all exceptions related to check errors.
|
10
|
+
module CheckError; end
|
11
|
+
|
12
|
+
# If a protocol check failes this exception is raised.
|
13
|
+
class BaseCheckError < ProtocolError
|
14
|
+
include CheckError
|
15
|
+
|
16
|
+
def initialize(protocol_message, message)
|
17
|
+
super(message)
|
18
|
+
@protocol_message = protocol_message
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the Protocol::Message object, that caused this CheckError to be
|
22
|
+
# raised.
|
23
|
+
attr_reader :protocol_message
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{protocol_message}: #{super}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<#{self.class.name}: #{to_s}>"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# This exception is raised if a method was not implented in a class, that was
|
35
|
+
# required to conform to the checked protocol.
|
36
|
+
class NotImplementedErrorCheckError < BaseCheckError; end
|
37
|
+
|
38
|
+
# This exception is raised if a method implented in a class didn't have the
|
39
|
+
# required arity, that was required to conform to the checked protocol.
|
40
|
+
class ArgumentErrorCheckError < BaseCheckError; end
|
41
|
+
|
42
|
+
# This exception is raised if a method implented in a class didn't have the
|
43
|
+
# expected block argument, that was required to conform to the checked
|
44
|
+
# protocol.
|
45
|
+
class BlockCheckError < BaseCheckError; end
|
46
|
+
|
47
|
+
# This exception is raised if a precondition check failed (the yielded
|
48
|
+
# block returned a non-true value) in a protocol description.
|
49
|
+
class PreconditionCheckError < BaseCheckError; end
|
50
|
+
|
51
|
+
# This exception is raised if a postcondition check failed (the yielded block
|
52
|
+
# returned a non-true value) in a protocol description.
|
53
|
+
class PostconditionCheckError < BaseCheckError; end
|
54
|
+
|
55
|
+
# This exception collects CheckError exceptions and mixes in Enumerable for
|
56
|
+
# further processing of them.
|
57
|
+
class CheckFailed < ProtocolError
|
58
|
+
include CheckError
|
59
|
+
|
60
|
+
def initialize(*errors)
|
61
|
+
@errors = errors
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :errors
|
65
|
+
|
66
|
+
# Return true, if this CheckFailed doesn't contain any errors (yet).
|
67
|
+
# Otherwise false is returned.
|
68
|
+
def empty?
|
69
|
+
errors.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add +check_error+ to this CheckFailed instance.
|
73
|
+
def <<(check_error)
|
74
|
+
@errors << check_error
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
# Iterate over all errors of this CheckFailed instance and pass each one to
|
79
|
+
# +block+.
|
80
|
+
def each_error(&block)
|
81
|
+
errors.each(&block)
|
82
|
+
end
|
83
|
+
|
84
|
+
alias each each_error
|
85
|
+
include Enumerable
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
errors * "|"
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect
|
92
|
+
"#<#{self.class.name}: #{errors.map { |e| e.inspect} * '|'}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module Protocol
|
2
|
+
# A Message consists of the name of the message (=method name), and the
|
3
|
+
# method argument's arity.
|
4
|
+
class Message
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
# Creates a Message instance named +name+, with the arity +arity+.
|
8
|
+
# If +arity+ is nil, the arity isn't checked during conformity tests.
|
9
|
+
def initialize(protocol, name, arity = nil, block_expected = false)
|
10
|
+
name = name.to_s
|
11
|
+
@protocol, @name, @arity, @block_expected =
|
12
|
+
protocol, name, arity, !!block_expected
|
13
|
+
end
|
14
|
+
|
15
|
+
# The protocol this message was defined in.
|
16
|
+
attr_reader :protocol
|
17
|
+
|
18
|
+
# Name of this message.
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# Arity of this message = the number of arguments.
|
22
|
+
attr_accessor :arity
|
23
|
+
|
24
|
+
# Set to true if this message should expect a block.
|
25
|
+
def block_expected=(block_expected)
|
26
|
+
@block_expected = !!block_expected
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true if this message is expected to include a block argument.
|
30
|
+
def block_expected?
|
31
|
+
@block_expected
|
32
|
+
end
|
33
|
+
|
34
|
+
# Message order is alphabetic name order.
|
35
|
+
def <=>(other)
|
36
|
+
name <=> other.name
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if this message equals the message +other+.
|
40
|
+
def ==(other)
|
41
|
+
name == other.name && arity == other.arity
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the shortcut for this message of the form "methodname(arity)".
|
45
|
+
def shortcut
|
46
|
+
"#{name}(#{arity}#{block_expected? ? '&' : ''})"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return a string representation of this message, in the form
|
50
|
+
# Protocol#name(arity).
|
51
|
+
def to_s
|
52
|
+
"#{protocol.name}##{shortcut}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Concatenates a method signature as ruby code to the +result+ string and
|
56
|
+
# returns it.
|
57
|
+
def to_ruby(result = '')
|
58
|
+
if arity
|
59
|
+
result << " def #{name}("
|
60
|
+
args = if arity >= 0
|
61
|
+
(1..arity).map { |i| "x#{i}" }
|
62
|
+
else
|
63
|
+
(1..~arity).map { |i| "x#{i}" } << '*rest'
|
64
|
+
end
|
65
|
+
if block_expected?
|
66
|
+
args << '&block'
|
67
|
+
end
|
68
|
+
result << args * ', '
|
69
|
+
result << ") end\n"
|
70
|
+
else
|
71
|
+
result << " understand :#{name}\n"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# The class +klass+ is checked against this Message instance. A CheckError
|
76
|
+
# exception will called, if either a required method isn't found in the
|
77
|
+
# +klass+, or it doesn't have the required arity (if a fixed arity was
|
78
|
+
# demanded).
|
79
|
+
def check(object, checked)
|
80
|
+
check_message = object.is_a?(Class) ? :check_class : :check_object
|
81
|
+
if checked.key?(name)
|
82
|
+
true
|
83
|
+
else
|
84
|
+
checked[name] = __send__(check_message, object)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Check class +klass+ against this Message instance, and raise a CheckError
|
91
|
+
# exception if necessary.
|
92
|
+
def check_class(klass)
|
93
|
+
unless klass.method_defined?(name)
|
94
|
+
raise NotImplementedErrorCheckError.new(self,
|
95
|
+
"method '#{name}' not implemented in #{klass}")
|
96
|
+
end
|
97
|
+
check_method = klass.instance_method(name)
|
98
|
+
if arity and (check_arity = check_method.arity) != arity
|
99
|
+
raise ArgumentErrorCheckError.new(self,
|
100
|
+
"wrong number of arguments for protocol"\
|
101
|
+
" in method '#{name}' (#{check_arity} for #{arity}) of #{klass}")
|
102
|
+
end
|
103
|
+
if block_expected?
|
104
|
+
modul = Utilities.find_method_module(name, klass.ancestors)
|
105
|
+
parser = MethodParser.new(modul, name)
|
106
|
+
parser.block_arg? or raise BlockCheckError.new(self,
|
107
|
+
"expected a block argument for #{klass}")
|
108
|
+
end
|
109
|
+
arity and wrap_method(klass)
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
# :stopdoc:
|
114
|
+
MyArray = Array.dup # Hack to make checking against Array possible.
|
115
|
+
# :startdoc:
|
116
|
+
|
117
|
+
def wrap_method(klass)
|
118
|
+
check_name = "__protocol_check_#{name}"
|
119
|
+
if klass.method_defined?(check_name)
|
120
|
+
inner_name = "__protocol_inner_#{name}"
|
121
|
+
unless klass.method_defined?(inner_name)
|
122
|
+
args =
|
123
|
+
if arity >= 0
|
124
|
+
(1..arity).map { |i| "x#{i}," }
|
125
|
+
else
|
126
|
+
(1..~arity).map { |i| "x#{i}," } << '*rest,'
|
127
|
+
end.join
|
128
|
+
wrapped_call = %{
|
129
|
+
alias_method :'#{inner_name}', :'#{name}'
|
130
|
+
|
131
|
+
def precondition
|
132
|
+
yield or
|
133
|
+
raise Protocol::PreconditionCheckError.new(
|
134
|
+
ObjectSpace._id2ref(#{__id__}),
|
135
|
+
"precondition failed for \#{self.class}")
|
136
|
+
end unless method_defined?(:precondition)
|
137
|
+
|
138
|
+
def postcondition(&block)
|
139
|
+
post_name = "__protocol_#{klass.__id__.abs}_postcondition__"
|
140
|
+
(Thread.current[post_name][-1] ||= Protocol::Postcondition.new(
|
141
|
+
self)).__add__ block
|
142
|
+
end unless method_defined?(:postcondition)
|
143
|
+
|
144
|
+
def #{name}(#{args} &block)
|
145
|
+
result = nil
|
146
|
+
post_name = "__protocol_#{klass.__id__.abs}_postcondition__"
|
147
|
+
(Thread.current[post_name] ||= MyArray.new) << nil
|
148
|
+
__send__('#{check_name}', #{args} &block)
|
149
|
+
if postcondition = Thread.current[post_name].last
|
150
|
+
begin
|
151
|
+
reraised = false
|
152
|
+
result = __send__('#{inner_name}', #{args} &block)
|
153
|
+
postcondition.__result__= result
|
154
|
+
rescue Protocol::PostconditionCheckError => e
|
155
|
+
reraised = true
|
156
|
+
raise e
|
157
|
+
ensure
|
158
|
+
unless reraised
|
159
|
+
postcondition.__check__ or
|
160
|
+
raise Protocol::PostconditionCheckError.new(
|
161
|
+
ObjectSpace._id2ref(#{__id__}),
|
162
|
+
"postcondition failed for \#{self.class}, result = " +
|
163
|
+
result.inspect)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
else
|
167
|
+
result = __send__('#{inner_name}', #{args} &block)
|
168
|
+
end
|
169
|
+
result
|
170
|
+
rescue Protocol::CheckError => e
|
171
|
+
case ObjectSpace._id2ref(#{__id__}).protocol.mode
|
172
|
+
when :error
|
173
|
+
raise e
|
174
|
+
when :warning
|
175
|
+
warn e
|
176
|
+
end
|
177
|
+
ensure
|
178
|
+
Thread.current[post_name].pop
|
179
|
+
Thread.current[post_name].empty? and
|
180
|
+
Thread.current[post_name] = nil
|
181
|
+
end
|
182
|
+
}
|
183
|
+
klass.class_eval wrapped_call
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Check object +object+ against this Message instance, and raise a
|
189
|
+
# CheckError exception if necessary.
|
190
|
+
def check_object(object)
|
191
|
+
if !object.respond_to?(name)
|
192
|
+
raise NotImplementedErrorCheckError.new(self,
|
193
|
+
"method '#{name}' not responding in #{object}")
|
194
|
+
end
|
195
|
+
check_method = object.method(name)
|
196
|
+
if arity and (check_arity = check_method.arity) != arity
|
197
|
+
raise ArgumentErrorCheckError.new(self,
|
198
|
+
"wrong number of arguments for protocol"\
|
199
|
+
" in method '#{name}' (#{check_arity} for #{arity}) of #{object}")
|
200
|
+
end
|
201
|
+
if block_expected?
|
202
|
+
if object.singleton_methods(false).map { |m| m.to_s } .include?(name)
|
203
|
+
parser = MethodParser.new(object, name, true)
|
204
|
+
else
|
205
|
+
ancestors = object.class.ancestors
|
206
|
+
modul = Utilities.find_method_module(name, ancestors)
|
207
|
+
parser = MethodParser.new(modul, name)
|
208
|
+
end
|
209
|
+
parser.block_arg? or raise BlockCheckError.new(self,
|
210
|
+
"expected a block argument for #{object}:#{object.class}")
|
211
|
+
end
|
212
|
+
if arity and not protocol === object
|
213
|
+
object.extend protocol
|
214
|
+
wrap_method(class << object ; self ; end)
|
215
|
+
end
|
216
|
+
true
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|