wanabe-definition 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +3 -0
- data/README +38 -0
- data/definition.gemspec +12 -0
- data/lib/definition.rb +239 -0
- data/test/tc_definition.rb +192 -0
- metadata +57 -0
data/Changelog
ADDED
data/README
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
definition
|
2
|
+
==========
|
3
|
+
|
4
|
+
Type striction for Ruby.
|
5
|
+
|
6
|
+
WARNING
|
7
|
+
-------
|
8
|
+
This library is very experimental.
|
9
|
+
Everything is under construction and fluid.
|
10
|
+
|
11
|
+
How to use
|
12
|
+
----------
|
13
|
+
|
14
|
+
like this:
|
15
|
+
|
16
|
+
module IFoo
|
17
|
+
as_interface
|
18
|
+
define::foo(Integer, String, Rest, Block)
|
19
|
+
end
|
20
|
+
class Foo
|
21
|
+
implement IFoo
|
22
|
+
end
|
23
|
+
Foo.new #=> NotImplementedError
|
24
|
+
|
25
|
+
Licence
|
26
|
+
-------
|
27
|
+
Modified-BSD Licence.
|
28
|
+
|
29
|
+
Copyright (c) 2009, wanabe
|
30
|
+
All rights reserved.
|
31
|
+
|
32
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
33
|
+
|
34
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
35
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
36
|
+
* Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
37
|
+
|
38
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/definition.gemspec
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "definition"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.date = "2009-02-13"
|
5
|
+
s.summary = ""
|
6
|
+
s.email = "s.wanabe@gmail.com"
|
7
|
+
s.homepage = "http://github.com/wanabe/definition"
|
8
|
+
s.description = ""
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = [""]
|
11
|
+
s.files = ["Changelog", "README", "definition.gemspec", "lib/definition.rb", "test/tc_definition.rb"]
|
12
|
+
end
|
data/lib/definition.rb
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
#! /usr/local/bin/ruby
|
2
|
+
# -*- coding: UTF-8 -*-
|
3
|
+
|
4
|
+
class DefinitionError < StandardError
|
5
|
+
end
|
6
|
+
class Definition
|
7
|
+
module Const
|
8
|
+
module Block
|
9
|
+
end
|
10
|
+
module Rest
|
11
|
+
end
|
12
|
+
module Any
|
13
|
+
def self.===(other)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class Enroller
|
19
|
+
def initialize(defn, table)
|
20
|
+
@defn = defn
|
21
|
+
@table = table
|
22
|
+
end
|
23
|
+
def method_missing(name, *args)
|
24
|
+
@defn.set(args)
|
25
|
+
@table[name] ||= []
|
26
|
+
@table[name] << @defn
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :arity
|
31
|
+
def initialize(ret_type, root)
|
32
|
+
@ret_type = ret_type
|
33
|
+
@root = root
|
34
|
+
end
|
35
|
+
def to_enroll(table)
|
36
|
+
Enroller.new(self, table)
|
37
|
+
end
|
38
|
+
def assert_match(name, meth, implement)
|
39
|
+
begin
|
40
|
+
if meth.respond_to?(:parameters)
|
41
|
+
if meth.parameters.length != @arg_types.length
|
42
|
+
message = "%s: arity mismatch (%i for %i)"
|
43
|
+
raise DefinitionError, message, name, meth.parameters.length,
|
44
|
+
@arg_types.length
|
45
|
+
end
|
46
|
+
meth.parameters.each_with_index do |param, i|
|
47
|
+
type, var = param
|
48
|
+
arg_type = @arg_types[i]
|
49
|
+
case type
|
50
|
+
when :req, :opt
|
51
|
+
next if arg_type != Const::Rest && arg_type != Const::Block
|
52
|
+
when :rest
|
53
|
+
next if arg_type == Const::Rest
|
54
|
+
when :block
|
55
|
+
next if arg_type == Const::Block
|
56
|
+
end
|
57
|
+
raise DefinitionError, "%s: arg %i: definition mismatch", name, i
|
58
|
+
end
|
59
|
+
elsif meth.arity != @arity
|
60
|
+
message = "%s: arity mismatch (%i for %i)"
|
61
|
+
raise DefinitionError, message, name, meth.arity, @arity
|
62
|
+
end
|
63
|
+
rescue DefinitionError
|
64
|
+
implement.module_eval { remove_method(name) }
|
65
|
+
Kernel.raise
|
66
|
+
end
|
67
|
+
end
|
68
|
+
def set(arg_types)
|
69
|
+
@arg_types = arg_types
|
70
|
+
@arity = arg_types.length
|
71
|
+
if arg_types.last == Const::Block
|
72
|
+
@arity -= 1
|
73
|
+
@block = true
|
74
|
+
end
|
75
|
+
@arity = -@arity if arg_types.include?(Const::Rest)
|
76
|
+
end
|
77
|
+
def assert_args(args)
|
78
|
+
start_index = 0
|
79
|
+
length = args.length
|
80
|
+
args.each_with_index do |arg, i|
|
81
|
+
next if i < start_index
|
82
|
+
break if i >= length
|
83
|
+
arg_type = @arg_types[i]
|
84
|
+
if arg_type == Definition::Const::Rest
|
85
|
+
start_index = @arity + args.length + i + 1
|
86
|
+
elsif !(arg_type === arg)
|
87
|
+
raise ArgumentError, "arg %i: not %p === %p", i, arg_type, arg
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
def assert_ret(ret)
|
92
|
+
ret_type = @ret_type
|
93
|
+
unless ret_type === ret
|
94
|
+
raise TypeError, "return value: not %p === %p", ret_type, ret
|
95
|
+
end
|
96
|
+
end
|
97
|
+
def raise(exclass, message, *args)
|
98
|
+
message = sprintf(message, *args)
|
99
|
+
if @root.ancestors.include? Interface
|
100
|
+
message << " (interface #{@root})"
|
101
|
+
else
|
102
|
+
message << " (implementation #{@root})"
|
103
|
+
end
|
104
|
+
super exclass, message
|
105
|
+
end
|
106
|
+
end
|
107
|
+
module Interface
|
108
|
+
include Definition::Const
|
109
|
+
module InterfaceMethods
|
110
|
+
def included(mod)
|
111
|
+
mod.extend InterfaceMethods
|
112
|
+
mod.init_table @define_table
|
113
|
+
end
|
114
|
+
def init_table(table)
|
115
|
+
@define_table ||= {}
|
116
|
+
if table
|
117
|
+
table.each do |name, defs|
|
118
|
+
@define_table[name] = defs.dup
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
def each(&block)
|
123
|
+
@define_table.each(&block)
|
124
|
+
end
|
125
|
+
def define(ret=Definition::Const::Any)
|
126
|
+
defn = Definition.new(ret, self)
|
127
|
+
return defn.to_enroll(@define_table)
|
128
|
+
end
|
129
|
+
def [](name)
|
130
|
+
return @define_table[name]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
extend InterfaceMethods
|
134
|
+
end
|
135
|
+
module Implementation
|
136
|
+
include Definition::Const
|
137
|
+
def self.included(klass)
|
138
|
+
klass.module_eval do
|
139
|
+
extend Implementation::ImplMethods
|
140
|
+
impl_initialize
|
141
|
+
end
|
142
|
+
end
|
143
|
+
module ImplMethods
|
144
|
+
NEW = Class.instance_method(:new)
|
145
|
+
def new
|
146
|
+
assert_implemented unless @skip_assertion
|
147
|
+
NEW.bind(self).call
|
148
|
+
end
|
149
|
+
def define(ret=Definition::Const::Any)
|
150
|
+
defn = Definition.new(ret, self)
|
151
|
+
return defn.to_enroll(@define_table)
|
152
|
+
end
|
153
|
+
def implement(interface)
|
154
|
+
interface.each do |name, defs|
|
155
|
+
@define_table[name] ||= []
|
156
|
+
@define_table[name] += defs
|
157
|
+
end
|
158
|
+
end
|
159
|
+
def assert_implemented(once = false)
|
160
|
+
nodefs = @define_table.keys - instance_methods(true).map{|m|m.to_sym}
|
161
|
+
unless nodefs.empty?
|
162
|
+
raise NotImplementedError, "not implemented #{nodefs.join(', ')}"
|
163
|
+
end
|
164
|
+
@skip_assertion ||= once
|
165
|
+
end
|
166
|
+
def assert_args(name, args)
|
167
|
+
defns = @define_table[name]
|
168
|
+
defns.each {|defn| defn.assert_args(args)}
|
169
|
+
end
|
170
|
+
def assert_ret(name, ret)
|
171
|
+
defns = @define_table[name]
|
172
|
+
defns.each {|defn| defn.assert_ret(ret)}
|
173
|
+
return ret
|
174
|
+
end
|
175
|
+
def impl_initialize(table = {})
|
176
|
+
@define_table = table
|
177
|
+
end
|
178
|
+
private
|
179
|
+
def included(klass)
|
180
|
+
klass.extend Implementation::ImplMethods
|
181
|
+
inherited(klass)
|
182
|
+
end
|
183
|
+
def inherited(klass)
|
184
|
+
klass.impl_initialize(@define_table)
|
185
|
+
assert_implemented
|
186
|
+
end
|
187
|
+
def method_added(name)
|
188
|
+
return if @in_addition_check
|
189
|
+
begin
|
190
|
+
@in_addition_check = true
|
191
|
+
check_definition(name)
|
192
|
+
add_assertion(name)
|
193
|
+
ensure
|
194
|
+
@in_addition_check = false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
def check_definition(name)
|
198
|
+
@define_table[name].each do |defn|
|
199
|
+
defn.assert_match name, instance_method(name), self
|
200
|
+
end
|
201
|
+
end
|
202
|
+
def add_assertion(name)
|
203
|
+
num = 0
|
204
|
+
new_name = name
|
205
|
+
new_name = "#{name}_#{num += 1}" while method_defined?(new_name)
|
206
|
+
alias_method new_name, name
|
207
|
+
module_eval <<-EOC, __FILE__, __LINE__ + 1
|
208
|
+
def #{name}(*args, &block)
|
209
|
+
self.class.assert_args(:#{name}, args)
|
210
|
+
self.class.assert_ret(:#{name}, #{new_name}(*args, &block))
|
211
|
+
end
|
212
|
+
EOC
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
class Module
|
217
|
+
def implement(*interfaces)
|
218
|
+
as_implementation
|
219
|
+
interfaces.each do |interface|
|
220
|
+
implement(interface)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
def define(ret=Definition::Const::Any)
|
224
|
+
as_implementation
|
225
|
+
define(ret)
|
226
|
+
end
|
227
|
+
private
|
228
|
+
def as_implementation
|
229
|
+
include Implementation unless include?(Implementation)
|
230
|
+
end
|
231
|
+
def as_interface
|
232
|
+
include Interface unless include?(Interface)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
if __FILE__ == $0
|
237
|
+
$" << "definition.rb"
|
238
|
+
load "../test/tc_definition.rb"
|
239
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
require 'test/unit'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'definition'
|
5
|
+
|
6
|
+
class TC_Foo < Test::Unit::TestCase
|
7
|
+
module IFoo
|
8
|
+
as_interface
|
9
|
+
define::foo(Integer, String, Rest, Block)
|
10
|
+
end
|
11
|
+
module IBar
|
12
|
+
as_interface
|
13
|
+
define(Any).bar(Integer)
|
14
|
+
end
|
15
|
+
module IFooBar
|
16
|
+
include IFoo, IBar
|
17
|
+
define.foo(10..20, Any, Rest, Block)
|
18
|
+
end
|
19
|
+
module IBar2
|
20
|
+
include IBar
|
21
|
+
define(Any).bar(0..10)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_arity
|
25
|
+
mismatch_arity_class = Class.new
|
26
|
+
mismatch_arity_class.implement IFoo
|
27
|
+
|
28
|
+
assert_raise(DefinitionError) do
|
29
|
+
mismatch_arity_class.module_eval do
|
30
|
+
implement IFoo
|
31
|
+
def foo(a, b, c, *d, &e)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
assert_raise(DefinitionError) do
|
36
|
+
mismatch_arity_class.module_eval do
|
37
|
+
def foo(a, *b, &c)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
def test_after_rescue
|
43
|
+
defective_module = Module.new
|
44
|
+
defective_class = Class.new
|
45
|
+
defective_module.implement IFoo
|
46
|
+
|
47
|
+
assert_raise(NotImplementedError) do
|
48
|
+
defective_class.module_eval do
|
49
|
+
include defective_module
|
50
|
+
end
|
51
|
+
end
|
52
|
+
assert_raise(NotImplementedError) do
|
53
|
+
defective_class.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def test_rest
|
57
|
+
mismatch_rest_class1 = Class.new
|
58
|
+
mismatch_rest_class1.implement IFoo
|
59
|
+
mismatch_rest_class2 = Class.new
|
60
|
+
mismatch_rest_class2.implement IBar
|
61
|
+
|
62
|
+
assert_raise(DefinitionError) do
|
63
|
+
mismatch_rest_class1.module_eval do
|
64
|
+
def foo(a, b, c, &d)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
assert_raise(DefinitionError) do
|
69
|
+
mismatch_rest_class1.module_eval do
|
70
|
+
def foo(a, b, &d)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
assert_raise(DefinitionError) do
|
75
|
+
mismatch_rest_class2.module_eval do
|
76
|
+
def bar(a, *b)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
assert_raise(DefinitionError) do
|
81
|
+
mismatch_rest_class2.module_eval do
|
82
|
+
def bar(*a)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
def test_correct
|
88
|
+
correct_class = Class.new
|
89
|
+
correct_class.implement IFoo
|
90
|
+
|
91
|
+
assert_nothing_raised do
|
92
|
+
correct_class.module_eval do
|
93
|
+
def foo(a, b, *c, &d)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
correct_class.new.foo(1, "a")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
def test_defective
|
100
|
+
defective_class = Class.new
|
101
|
+
defective_class.module_eval do
|
102
|
+
define(String).baz
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_raise(NotImplementedError) do
|
106
|
+
Class.new(defective_class)
|
107
|
+
end
|
108
|
+
assert_raise(NotImplementedError) do
|
109
|
+
defective_class.new
|
110
|
+
end
|
111
|
+
end
|
112
|
+
def test_retval
|
113
|
+
baz_class = Class.new
|
114
|
+
baz_class.module_eval do
|
115
|
+
define(String).baz
|
116
|
+
def baz
|
117
|
+
:ng
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
assert_raise(TypeError) do
|
122
|
+
baz_class.new.baz
|
123
|
+
end
|
124
|
+
baz_class.module_eval do
|
125
|
+
def baz
|
126
|
+
"ok"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
assert_equal(baz_class.new.baz, "ok")
|
130
|
+
end
|
131
|
+
def test_argument
|
132
|
+
foobar_class = Class.new
|
133
|
+
foobar_class.module_eval do
|
134
|
+
implement IFooBar, IBar2
|
135
|
+
def foo(a, b, *c, &d)
|
136
|
+
end
|
137
|
+
def bar(a)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
o = foobar_class.new
|
141
|
+
assert_raise(ArgumentError) do
|
142
|
+
o.foo(1,"a")
|
143
|
+
end
|
144
|
+
assert_raise(ArgumentError) do
|
145
|
+
o.foo(10,2)
|
146
|
+
end
|
147
|
+
assert_raise(ArgumentError) do
|
148
|
+
o.bar(:ng)
|
149
|
+
end
|
150
|
+
assert_raise(ArgumentError) do
|
151
|
+
o.bar(-1)
|
152
|
+
end
|
153
|
+
assert_nothing_raised do
|
154
|
+
o.foo(10,"a")
|
155
|
+
o.bar(1)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
if RUBY_VERSION >= "1.9"
|
159
|
+
def test_arg_after_rest
|
160
|
+
class_arg_after_rest = Class.new
|
161
|
+
class_arg_after_rest.module_eval do
|
162
|
+
define.foo(Any, Rest, Range)
|
163
|
+
end
|
164
|
+
assert_raise(DefinitionError) do
|
165
|
+
class_arg_after_rest.module_eval do
|
166
|
+
def foo(a, *b)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
assert_raise(DefinitionError) do
|
171
|
+
class_arg_after_rest.module_eval do
|
172
|
+
def foo(a, b, *c)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
assert_nothing_raised do
|
177
|
+
class_arg_after_rest.module_eval do
|
178
|
+
eval "def foo(a, *b, c);end"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
def test_block
|
183
|
+
assert_raise(DefinitionError) do
|
184
|
+
Class.new.module_eval do
|
185
|
+
define.foo
|
186
|
+
def foo(&a)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wanabe-definition
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ""
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-13 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: s.wanabe@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- Changelog
|
26
|
+
- README
|
27
|
+
- definition.gemspec
|
28
|
+
- lib/definition.rb
|
29
|
+
- test/tc_definition.rb
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://github.com/wanabe/definition
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.2.0
|
53
|
+
signing_key:
|
54
|
+
specification_version: 2
|
55
|
+
summary: ""
|
56
|
+
test_files: []
|
57
|
+
|