typedocs 0.0.1

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.
@@ -0,0 +1,70 @@
1
+ require 'parslet'
2
+
3
+ class Typedocs::Parser::ASTBuilder < Parslet::Parser
4
+ root :method_spec
5
+
6
+ rule(:method_spec) { spaces >> rep1(method_spec1, s('||')).as(:method_spec) }
7
+ rule(:method_spec1) { (arg_spec >> s('->')).repeat.as(:arg_specs) >> (block_spec >> s('->')).maybe.as(:block_spec) >> return_spec.maybe.as(:return_spec) }
8
+
9
+ rule(:arg_spec) { arg_attr.maybe.as(:attr) >> named_type }
10
+ rule(:arg_name) { v(match['_a-z0-9?!'].repeat(1)) >> spaces }
11
+ rule(:arg_attr) { match['*?'] }
12
+ rule(:named_type) {type.as(:type) | arg_name.as(:name) >> (s(':') >> type).maybe.as(:type) }
13
+
14
+ rule(:block_spec) { s('?').maybe.as(:attr) >> s('&') >> arg_name.maybe.as(:name) }
15
+
16
+ rule(:return_spec) { named_type }
17
+
18
+ rule(:type) { rep1(type1, s('|') >> s('|').absent?) }
19
+ rule(:type1) {
20
+ t(:type_name) | t(:defined_type_name) | t(:any) | t(:void) | t(:array) | t(:tuple) | hashes | values
21
+ }
22
+
23
+ rule(:type_name) { str('::').maybe >> v(rep1(match['A-Z'] >> match['A-Za-z0-9_'].repeat, str('::'))) >> spaces }
24
+ rule(:defined_type_name) { str('@') >> type_name }
25
+
26
+ rule(:any) { s('_') }
27
+ rule(:void) { s('void') | s('--') }
28
+
29
+ rule(:array) { s('[') >> named_type >> s('...') >> s(']') }
30
+ rule(:tuple) { s('[') >> rep0(named_type, s(',')).as(:types) >> s(']') }
31
+
32
+ rule(:hashes) { t(:hash_v) | t(:hash_t) }
33
+ rule(:hash_t) { s('{') >> named_type.as(:key_t) >> s('=>') >> named_type.as(:val_t) >> s('}') }
34
+ rule(:hash_v) { s('{') >> rep1(hash_v_entry, s(',')).as(:entries) >> (s(',') >> s('...')).maybe.as(:anymore) >> s('}') }
35
+ rule(:hash_v_entry) { values.as(:key_v) >> s("=>") >> named_type.as(:val_t) }
36
+
37
+ rule(:values) { t(:nil_value) | t(:string_value) | t(:symbol_value) }
38
+ rule(:nil_value) { s('nil') }
39
+ rule(:string_value) { string_value_sq | string_value_dq }
40
+ rule(:string_value_sq) { s("'") >> v((match["^'"] | str("\\'")).repeat) >> s("'") }
41
+ rule(:string_value_dq) { s('"') >> v((match['^"'] | str('\\"')).repeat) >> s('"') }
42
+ rule(:symbol_value) { str(':') >> v(match['A-Za-z_'] >> match['A-Za-z0-9_'].repeat >> match['?!'].maybe) >> spaces }
43
+
44
+ rule(:spaces) { match('\\s').repeat }
45
+
46
+ private
47
+ def s(string)
48
+ str(string) >> spaces
49
+ end
50
+
51
+ def sv(string)
52
+ str(string).as(:value) >> spaces
53
+ end
54
+
55
+ def v(rule)
56
+ rule.as(:value)
57
+ end
58
+
59
+ def t(rule_name)
60
+ send(rule_name).as(rule_name)
61
+ end
62
+
63
+ def rep0(rule, separator)
64
+ rep1(rule, separator).repeat(0)
65
+ end
66
+
67
+ def rep1(rule, separator)
68
+ rule >> (separator >> rule).repeat
69
+ end
70
+ end
@@ -0,0 +1,104 @@
1
+ class Typedocs::Parser; end
2
+
3
+ require 'parslet'
4
+ require 'typedocs/type_spec'
5
+
6
+ class Typedocs::Parser::ObjectBuilder
7
+ module Helper
8
+ def self.array(a)
9
+ case a
10
+ when Array
11
+ a
12
+ else
13
+ [a]
14
+ end
15
+ end
16
+ end
17
+ def self.create_builder_for(klass)
18
+ Parslet::Transform.new do
19
+ val = {value: simple(:v)}
20
+ ts = Typedocs::TypeSpec
21
+ h = Helper
22
+ dc = subtree(:_) # dont care
23
+ dc2 = subtree(:__)
24
+ mktype = ->(t, name) {
25
+ unnamed =
26
+ case t
27
+ when Array
28
+ ts::Or.new(t)
29
+ else
30
+ t || ts::Any.new
31
+ end
32
+ if name
33
+ ts::Named.new(name, unnamed)
34
+ else
35
+ unnamed
36
+ end
37
+ }
38
+
39
+ rule(method_spec: subtree(:ms)) {
40
+ specs = h.array(ms).map {|tree|
41
+ args_spec = Typedocs::ArgumentsSpec.new
42
+ tree[:arg_specs].each do|as|
43
+ type = as[:t] || Typedocs::TypeSpec::Any.new
44
+ case as[:a]
45
+ when '*'
46
+ args_spec.add_rest(type)
47
+ when '?'
48
+ args_spec.add_optional(type)
49
+ when nil
50
+ args_spec.add_required(type)
51
+ else
52
+ raise "Unknown attr: #{as[:a].inspect}"
53
+ end
54
+ end
55
+ return_spec = tree[:return_spec] || Typedocs::TypeSpec::Void.new
56
+ block_spec =
57
+ tree[:block_spec].tap do|bs|
58
+ break Typedocs::BlockSpec.new(:none, nil) if !bs
59
+ name = bs[:name] ? bs[:name][:value] : nil
60
+ if bs[:attr] == '?'
61
+ break Typedocs::BlockSpec.new(:opt, name)
62
+ else
63
+ break Typedocs::BlockSpec.new(:req, name)
64
+ end
65
+ end
66
+ Typedocs::MethodSpec::Single.new(args_spec, block_spec, return_spec)
67
+ }
68
+ if specs.size > 1
69
+ Typedocs::MethodSpec::AnyOf.new(specs)
70
+ else
71
+ specs.first
72
+ end
73
+ }
74
+
75
+ # arg
76
+ rule(type: subtree(:t), attr: simple(:attr)) { {t: mktype[t, nil], a: attr} }
77
+ rule(type: subtree(:t), name: {value: simple(:name)}, attr: simple(:attr)) { {t: mktype[t, name], a: attr} }
78
+ # return
79
+ rule(type: subtree(:t)) { mktype[t, nil] }
80
+ rule(type: subtree(:t), name: {value: simple(:name)}) { mktype[t, name] }
81
+
82
+ rule(type_name: val) { ts::TypeIsA.new(klass, v.to_s) }
83
+ rule(defined_type_name: val) { ts::UserDefinedType.new(klass, "@#{v.to_s}") }
84
+ rule(any: dc) { ts::Any.new }
85
+ rule(void: dc) { ts::Void.new }
86
+ rule(array: simple(:v)) { ts::Array.new(v) }
87
+ rule(tuple: {types: subtree(:vs)}) { ts::ArrayAsStruct.new(vs) }
88
+ rule(hash_t: {key_t: simple(:k), val_t: simple(:v)}) { ts::HashType.new(k,v) }
89
+ rule(hash_v: {entries: subtree(:entries), anymore: simple(:anymore)}) {
90
+ kvs = h.array(entries).map{|e|
91
+ k = e[:key_v]
92
+ v = e[:val_t]
93
+ key_v = k.value
94
+ [key_v, v]
95
+ }
96
+ ts::HashValue.new(kvs, !!anymore)
97
+ }
98
+
99
+ rule(string_value: val) { ts::Value.new(v.to_s) }
100
+ rule(symbol_value: val) { ts::Value.new(v.to_sym) }
101
+ rule(nil_value: dc) { ts::Value.new(nil) }
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,210 @@
1
+ class Typedocs::TypeSpec
2
+ def error_message_for(obj)
3
+ "Expected #{self.to_source}, but #{obj.inspect}"
4
+ end
5
+
6
+ class Named < self
7
+ def initialize(name, spec)
8
+ Typedocs.ensure_klass(spec, Typedocs::TypeSpec)
9
+ @name = name
10
+ @spec = spec
11
+ end
12
+ attr_reader :name
13
+ attr_reader :spec
14
+ def valid?(arg); spec.valid?(arg); end
15
+ def to_source; "#{name}:#{spec.to_source}"; end
16
+ def error_message_for(arg)
17
+ spec.error_message_for(arg)
18
+ end
19
+ end
20
+
21
+ class Any < self
22
+ def valid?(arg); true; end
23
+ def to_source; '_'; end
24
+ def error_message_for(arg)
25
+ raise "This spec accepts ANY value"
26
+ end
27
+ end
28
+ class Void < Any
29
+ def to_source; 'void' end
30
+ end
31
+ class TypeIsA < self
32
+ def initialize(klass, name)
33
+ @klass = klass
34
+ @name = name
35
+ end
36
+ def target_klass
37
+ @target_klass ||= find_const @klass, @name
38
+ end
39
+ def valid?(arg);
40
+ arg.is_a? target_klass
41
+ end
42
+ def to_source
43
+ @name
44
+ end
45
+ private
46
+ def find_const(start, name)
47
+ raise ::ArgumentError, 'name' if name.empty?
48
+ raise ::ArgumentError, 'start' unless start
49
+ case name
50
+ when /^::/
51
+ const_get_from! ::Object, name
52
+ else
53
+ candidates = []
54
+ candidates << const_get_from(start, name)
55
+ candidates << const_get_with_nested(start, name)
56
+ candidates = candidates.reject(&:nil?).uniq
57
+ raise ::ArgumentError, "Type name #{name} is ambigious(search base: #{start}): #{candidates.map(&:name).join(' and ')}" if candidates.size > 1
58
+ raise ::ArgumentError, "Type not found: #{name}(search base: #{start})" unless candidates.size == 1
59
+ candidates.first
60
+ end
61
+ end
62
+ def const_get_with_nested(start, name)
63
+ top = name.split(/::/).first
64
+ root = start
65
+ until root.nil?
66
+ return const_get_from(root, name) if root.const_defined?(top, false)
67
+ root = parent_nest(root)
68
+ end
69
+ nil
70
+ end
71
+ def parent_nest(klass)
72
+ return nil unless klass.name =~ /::/
73
+ name = klass.name.split(/::/)[0..-2].join('::')
74
+ const_get_from ::Object, name
75
+ end
76
+ def const_get_from(root, name)
77
+ begin
78
+ const_get_from! root, name
79
+ rescue NameError
80
+ nil
81
+ end
82
+ end
83
+ def const_get_from!(root, name)
84
+ name.gsub(/^::/,'').split(/::/).inject(root) do|root, name|
85
+ root.const_get(name.to_sym)
86
+ end
87
+ rescue NameError => e
88
+ raise NameError, "NameError: #{name.inspect}"
89
+ end
90
+ end
91
+ class Nil < self
92
+ def initialize
93
+ @value = nil
94
+ end
95
+ def valid?(obj)
96
+ obj == @value
97
+ end
98
+ def to_source
99
+ @value.inspect
100
+ end
101
+ def error_message_for(obj)
102
+ "#{obj} should == #{@value.inspect}"
103
+ end
104
+ end
105
+ class ArrayAsStruct < self
106
+ def initialize(specs)
107
+ specs.each {|s| Typedocs.ensure_klass(s, Typedocs::TypeSpec) }
108
+ @specs = specs
109
+ end
110
+ def valid?(obj)
111
+ obj.is_a?(::Array) &&
112
+ @specs.size == obj.size &&
113
+ @specs.zip(obj).all?{|spec,elm| spec.valid?(elm)}
114
+ end
115
+ def to_source
116
+ "[#{@specs.map(&:to_source).join(', ')}]"
117
+ end
118
+ end
119
+ class Array < self
120
+ def initialize(spec)
121
+ Typedocs.ensure_klass(spec, Typedocs::TypeSpec)
122
+ @spec = spec
123
+ end
124
+ def valid?(obj)
125
+ obj.is_a?(::Array) && obj.all?{|elm| @spec.valid?(elm)}
126
+ end
127
+ def to_source
128
+ "#{@spec.to_source}..."
129
+ end
130
+ end
131
+ class HashValue < self
132
+ # [key, spec]... ->
133
+ def initialize(entries, accept_others)
134
+ entries.each do|k, s|
135
+ Typedocs.ensure_klass(s, Typedocs::TypeSpec)
136
+ end
137
+ @entries = entries
138
+ @accept_others = accept_others
139
+ end
140
+ def valid?(obj)
141
+ obj.is_a?(::Hash) &&
142
+ (@accept_others || @entries.size == obj.size) &&
143
+ @entries.all? {|key, spec| obj.has_key?(key) && spec.valid?(obj[key]) }
144
+ end
145
+ def to_source
146
+ "{#{@entries.map{|key,value| "#{key.inspect} => #{value.to_source}"}.join(', ')}#{@accept_others ? ', ...' : ''}}"
147
+ end
148
+ end
149
+ class HashType < self
150
+ def initialize(key_spec, value_spec)
151
+ @key_spec = key_spec
152
+ @value_spec = value_spec
153
+ end
154
+ def valid?(obj)
155
+ obj.is_a?(::Hash) &&
156
+ obj.keys.all?{|k| @key_spec.valid? k} &&
157
+ obj.values.all?{|v| @value_spec.valid? v}
158
+ end
159
+ def to_source
160
+ "{#{@key_spec.to_source} => #{@value_spec.to_source}}"
161
+ end
162
+ end
163
+ class Or < self
164
+ def initialize(children)
165
+ raise ArgumentError, "Children is empty" if children.empty?
166
+ children.each do|c|
167
+ Typedocs.ensure_klass(c, Typedocs::TypeSpec)
168
+ end
169
+ @children = children
170
+ end
171
+ def valid?(obj)
172
+ @children.any?{|spec| spec.valid? obj}
173
+ end
174
+ def to_source
175
+ "#{@children.map(&:to_source).join('|')}"
176
+ end
177
+ end
178
+ class UserDefinedType < self
179
+ def initialize(klass, name, spec = nil)
180
+ raise ArgumentError, "Invalid UDT name: #{name.inspect}" unless Typedocs::Context.valid_udt_name?(name)
181
+ @klass = klass
182
+ @name = name
183
+ @spec = spec
184
+ end
185
+ attr_reader :klass
186
+ attr_reader :name
187
+ def spec
188
+ @spec ||= Typedocs.context(klass).defined_type!(name)
189
+ end
190
+ def valid?(arg)
191
+ spec.valid?(arg)
192
+ end
193
+ def to_source
194
+ name
195
+ end
196
+ end
197
+ class Value < self
198
+ def initialize(val)
199
+ @value = val
200
+ end
201
+ attr_reader :value
202
+ def valid?(obj)
203
+ obj == value
204
+ end
205
+ def to_source
206
+ value.inspect
207
+ end
208
+ end
209
+ end
210
+
@@ -0,0 +1,3 @@
1
+ module Typedocs
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,130 @@
1
+ load File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe 'Usage examples' do
4
+ describe 'Simple example' do
5
+ class SimpleExample
6
+ include Typedocs::DSL
7
+
8
+ tdoc "Numeric -> Numeric"
9
+ def square x
10
+ x * x
11
+ end
12
+ end
13
+
14
+ subject { SimpleExample.new }
15
+ it { subject.square(10).should == 100 }
16
+ it { expect { subject.square('10') }.to raise_error Typedocs::ArgumentError }
17
+ end
18
+ describe 'Basic usage' do
19
+ class BasicUsage
20
+ include Typedocs::DSL
21
+
22
+ tdoc "Numeric -> Numeric"
23
+ def square(x)
24
+ x * x
25
+ end
26
+
27
+ tdoc "String"
28
+ def expected_string_but_nil
29
+ nil
30
+ end
31
+
32
+ tdoc "Numeric -> [Integer,String]"
33
+ def return_pair(num)
34
+ [num.to_i, num.to_s]
35
+ end
36
+
37
+ tdoc "[]"
38
+ def return_empty_array
39
+ []
40
+ end
41
+
42
+ tdoc "_ -> String | Integer"
43
+ def return_int_or_string(is_str)
44
+ is_str ? 'string' : 100
45
+ end
46
+
47
+ tdoc "nil"
48
+ def return_nil
49
+ nil
50
+ end
51
+ end
52
+
53
+ describe BasicUsage do
54
+ subject { BasicUsage.new }
55
+ it do
56
+ subject.square(10).should == 100
57
+ end
58
+ it do
59
+ expect { subject.square('10') }.to raise_error Typedocs::ArgumentError
60
+ end
61
+ it do
62
+ expect { subject.expected_string_but_nil }.to raise_error Typedocs::RetValError
63
+ end
64
+ it do
65
+ subject.return_pair(1).should == [1, '1']
66
+ end
67
+
68
+ it do
69
+ subject.return_empty_array.should == []
70
+ end
71
+
72
+ it do
73
+ subject.return_int_or_string(true).should == 'string'
74
+ subject.return_int_or_string(false).should == 100
75
+ end
76
+ it do
77
+ subject.return_nil.should == nil
78
+ end
79
+ end
80
+ end
81
+ class Initialize
82
+ include Typedocs::DSL
83
+
84
+ tdoc "Integer ->"
85
+ def initialize(n)
86
+ end
87
+ end
88
+ describe Initialize do
89
+ it do
90
+ Initialize.new(1)
91
+ end
92
+ it do
93
+ expect { Initialize.new(nil) }.to raise_error Typedocs::ArgumentError
94
+ end
95
+ end
96
+
97
+ class KlassMethods
98
+ include Typedocs::DSL
99
+
100
+ tdoc "String->Integer"
101
+ def self.class_method(s); s.to_i; end
102
+
103
+ def untyped_instance_method(arg); arg; end
104
+ end
105
+ describe KlassMethods do
106
+ it do
107
+ KlassMethods.new.untyped_instance_method(1).should == 1
108
+ end
109
+ it do
110
+ expect { KlassMethods.class_method(1) }.to raise_error Typedocs::ArgumentError
111
+ end
112
+ end
113
+
114
+ class RelativeTypeNames
115
+ class Outer
116
+ include Typedocs::DSL
117
+
118
+ tdoc "A"
119
+ def self.a; A.new; end
120
+ end
121
+ class A; end
122
+ end
123
+ describe RelativeTypeNames do
124
+ it do
125
+ RelativeTypeNames::Outer.a.should be_is_a(RelativeTypeNames::A)
126
+ end
127
+ end
128
+
129
+ end
130
+