algebrick 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -3
  3. data/README_FULL.md +13 -21
  4. data/VERSION +1 -1
  5. data/doc/actor.rb +21 -0
  6. data/doc/data.in.rb +99 -0
  7. data/doc/data.out.rb +103 -0
  8. data/doc/extending_behavior.in.rb +61 -0
  9. data/doc/extending_behavior.out.rb +62 -0
  10. data/doc/format.rb +75 -0
  11. data/doc/init.rb +1 -0
  12. data/doc/json.in.rb +39 -0
  13. data/doc/json.out.rb +43 -0
  14. data/doc/null.in.rb +36 -0
  15. data/doc/null.out.rb +40 -0
  16. data/doc/parametrized.in.rb +37 -0
  17. data/doc/parametrized.out.rb +41 -0
  18. data/doc/pattern_matching.in.rb +116 -0
  19. data/doc/pattern_matching.out.rb +122 -0
  20. data/doc/quick_example.in.rb +27 -0
  21. data/doc/quick_example.out.rb +27 -0
  22. data/doc/tree1.in.rb +10 -0
  23. data/doc/tree1.out.rb +10 -0
  24. data/doc/type_def.in.rb +21 -0
  25. data/doc/type_def.out.rb +21 -0
  26. data/doc/values.in.rb +52 -0
  27. data/doc/values.out.rb +58 -0
  28. data/lib/algebrick/atom.rb +49 -0
  29. data/lib/algebrick/dsl.rb +104 -0
  30. data/lib/algebrick/field_method_readers.rb +43 -0
  31. data/lib/algebrick/matcher_delegations.rb +45 -0
  32. data/lib/algebrick/matchers/abstract.rb +127 -0
  33. data/lib/algebrick/matchers/abstract_logic.rb +38 -0
  34. data/lib/algebrick/matchers/and.rb +29 -0
  35. data/lib/algebrick/matchers/any.rb +37 -0
  36. data/lib/algebrick/matchers/array.rb +57 -0
  37. data/lib/algebrick/matchers/atom.rb +28 -0
  38. data/lib/algebrick/matchers/not.rb +44 -0
  39. data/lib/algebrick/matchers/or.rb +51 -0
  40. data/lib/algebrick/matchers/product.rb +73 -0
  41. data/lib/algebrick/matchers/variant.rb +29 -0
  42. data/lib/algebrick/matchers/wrapper.rb +57 -0
  43. data/lib/algebrick/matchers.rb +31 -0
  44. data/lib/algebrick/matching.rb +62 -0
  45. data/lib/algebrick/parametrized_type.rb +122 -0
  46. data/lib/algebrick/product_constructors/abstract.rb +70 -0
  47. data/lib/algebrick/product_constructors/basic.rb +47 -0
  48. data/lib/algebrick/product_constructors/named.rb +58 -0
  49. data/lib/algebrick/product_constructors.rb +25 -0
  50. data/lib/algebrick/product_variant.rb +195 -0
  51. data/lib/algebrick/reclude.rb +39 -0
  52. data/lib/algebrick/serializer.rb +129 -0
  53. data/lib/algebrick/serializers.rb +25 -0
  54. data/lib/algebrick/type.rb +61 -0
  55. data/lib/algebrick/type_check.rb +58 -0
  56. data/lib/algebrick/types.rb +59 -0
  57. data/lib/algebrick/value.rb +41 -0
  58. data/lib/algebrick.rb +14 -1170
  59. data/spec/algebrick_test.rb +708 -0
  60. metadata +105 -27
@@ -0,0 +1,29 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ module Matchers
17
+ class Variant < Wrapper
18
+ def initialize(something)
19
+ raise ArgumentError unless something.variants
20
+ Type! something, Algebrick::ProductVariant
21
+ super something
22
+ end
23
+
24
+ def to_s
25
+ assign_to_s + "#{@something.name}.to_m"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ module Matchers
17
+ # wraps any object having #=== method into matcher
18
+ class Wrapper < Abstract
19
+ def self.call(something)
20
+ new something
21
+ end
22
+
23
+ attr_reader :something
24
+
25
+ def initialize(something)
26
+ super()
27
+ @something = matchable! something
28
+ end
29
+
30
+ def children
31
+ find_children [@something]
32
+ end
33
+
34
+ def to_s
35
+ assign_to_s + "Wrapper.(#{@something})"
36
+ end
37
+
38
+ def ==(other)
39
+ other.kind_of? self.class and
40
+ self.something == other.something
41
+ end
42
+
43
+ protected
44
+
45
+ def matching?(other)
46
+ @something === other
47
+ end
48
+ end
49
+
50
+ # allow to convert any object to matcher if it has #=== method
51
+ class ::Object
52
+ def to_m
53
+ Wrapper.new(self)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ module Matchers
17
+
18
+ require 'algebrick/matchers/abstract'
19
+ require 'algebrick/matchers/abstract_logic'
20
+ require 'algebrick/matchers/and'
21
+ require 'algebrick/matchers/or'
22
+ require 'algebrick/matchers/not'
23
+ require 'algebrick/matchers/any'
24
+ require 'algebrick/matchers/wrapper'
25
+ require 'algebrick/matchers/array'
26
+ require 'algebrick/matchers/product'
27
+ require 'algebrick/matchers/variant'
28
+ require 'algebrick/matchers/atom'
29
+
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ # include this module anywhere yoy need to use pattern matching
17
+ module Matching
18
+ def any
19
+ Matchers::Any.new
20
+ end
21
+
22
+ def match(value, *cases)
23
+ cases = if cases.size == 1 && cases.first.is_a?(Hash)
24
+ cases.first
25
+ else
26
+ cases
27
+ end
28
+
29
+ cases.each do |matcher, block|
30
+ return Matching.match_value matcher, block if matcher === value
31
+ end
32
+ raise "no match for (#{value.class}) '#{value}' by any of #{cases.map(&:first).join ', '}"
33
+ end
34
+
35
+ def on(matcher, value = nil, &block)
36
+ matcher = if matcher.is_a? Matchers::Abstract
37
+ matcher
38
+ else
39
+ matcher.to_m
40
+ end
41
+ raise ArgumentError, 'only one of block or value can be supplied' if block && value
42
+ [matcher, value || block]
43
+ end
44
+
45
+ private
46
+
47
+ def self.match_value(matcher, block)
48
+ if block.kind_of? Proc
49
+ if matcher.kind_of? Matchers::Abstract
50
+ matcher.assigns &block
51
+ else
52
+ block.call
53
+ end
54
+ else
55
+ block
56
+ end
57
+ end
58
+ end
59
+
60
+ include Matching
61
+ extend Matching
62
+ end
@@ -0,0 +1,122 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ require 'monitor'
17
+
18
+ class ParametrizedType < Type
19
+ include TypeCheck
20
+ include MatcherDelegations
21
+ include FieldMethodReaders
22
+
23
+ attr_reader :variables, :fields, :variants
24
+
25
+ def initialize(variables)
26
+ @variables = variables.each { |v| Type! v, Symbol }
27
+ @fields = nil
28
+ @variants = nil
29
+ @cache = {}
30
+ @cache_barrier = Monitor.new
31
+ end
32
+
33
+ def set_fields(fields)
34
+ @fields = Type! fields, Hash, Array
35
+ @field_names = case @fields
36
+ when Hash
37
+ @fields.keys
38
+ when Array, nil
39
+ nil
40
+ else
41
+ raise
42
+ end
43
+ end
44
+
45
+ def set_variants(*variants)
46
+ @variants = Type! variants, Array
47
+ end
48
+
49
+ def [](*assigned_types)
50
+ @cache_barrier.synchronize do
51
+ @cache[assigned_types] || begin
52
+ raise ArgumentError unless assigned_types.size == variables.size
53
+ ProductVariant.new(type_name(assigned_types)).tap do |type|
54
+ type.be_kind_of self
55
+ @cache[assigned_types] = type
56
+ type.assigned_types = assigned_types
57
+ type.set_variants *insert_types(variants, assigned_types) if variants
58
+ type.set_fields insert_types(fields, assigned_types) if fields
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def generic
65
+ @generic ||= self[*Array(variables.size) { Object }]
66
+ end
67
+
68
+ def to_s
69
+ "#{name}[#{variables.join(', ')}]"
70
+ end
71
+
72
+ def inspect
73
+ to_s
74
+ end
75
+
76
+ def to_m
77
+ if @variants
78
+ Matchers::Variant.new self
79
+ else
80
+ Matchers::Product.new self
81
+ end
82
+ end
83
+
84
+ def call(*field_matchers)
85
+ raise TypeError unless @fields
86
+ Matchers::Product.new self, *field_matchers
87
+ end
88
+
89
+ def ==(other)
90
+ other.kind_of? ParametrizedType and
91
+ self.generic == other.generic
92
+ end
93
+
94
+ private
95
+
96
+ def insert_types(types, assigned_types)
97
+ case types
98
+ when Hash
99
+ types.inject({}) { |h, (k, v)| h.update k => insert_type(v, assigned_types) }
100
+ when Array
101
+ types.map { |v| insert_type v, assigned_types }
102
+ else
103
+ raise ArgumentError
104
+ end
105
+ end
106
+
107
+ def insert_type(type, assigned_types)
108
+ case type
109
+ when Symbol
110
+ assigned_types[variables.index type]
111
+ when ParametrizedType
112
+ type[*type.variables.map { |v| assigned_types[variables.index v] }]
113
+ else
114
+ type
115
+ end
116
+ end
117
+
118
+ def type_name(assigned_types)
119
+ "#{name}[#{assigned_types.join(', ')}]"
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ module ProductConstructors
17
+ class Abstract
18
+ include Value
19
+ extend TypeCheck
20
+ attr_reader :fields
21
+
22
+ def initialize(*fields)
23
+ if fields.size == 1 && fields.first.is_a?(Hash)
24
+ fields = type.field_names.map { |k| fields.first[k] }
25
+ end
26
+ @fields = fields.zip(self.class.type.fields).map { |field, type| Type! field, type }.freeze
27
+ end
28
+
29
+ def to_ary
30
+ @fields
31
+ end
32
+
33
+ def to_a
34
+ @fields
35
+ end
36
+
37
+ def ==(other)
38
+ return false unless other.kind_of? self.class
39
+ @fields == other.fields
40
+ end
41
+
42
+ def self.type
43
+ @type || raise
44
+ end
45
+
46
+ def type
47
+ self.class.type
48
+ end
49
+
50
+ def self.name
51
+ @type.to_s
52
+ end
53
+
54
+ def self.to_s
55
+ name
56
+ end
57
+
58
+ def self.type=(type)
59
+ Type! type, ProductVariant
60
+ raise if @type
61
+ @type = type
62
+ include type
63
+ end
64
+
65
+ def update(*fields)
66
+ raise NotImplementedError
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,47 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ module ProductConstructors
17
+ class Basic < Abstract
18
+ def to_s
19
+ "#{self.class.type.name}[" +
20
+ fields.map(&:to_s).join(', ') + ']'
21
+ end
22
+
23
+ def pretty_print(q)
24
+ q.group(1, "#{self.class.type.name}[", ']') do
25
+ fields.each_with_index do |value, i|
26
+ if i == 0
27
+ q.breakable ''
28
+ else
29
+ q.text ','
30
+ q.breakable ' '
31
+ end
32
+ q.pp value
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.type=(type)
38
+ super(type)
39
+ raise if type.field_names?
40
+ end
41
+
42
+ def update(fields)
43
+ type[*fields]
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ module ProductConstructors
17
+ class Named < Abstract
18
+ def to_s
19
+ "#{self.class.type.name}[" +
20
+ type.field_names.map { |name| "#{name}: #{self[name].to_s}" }.join(', ') +']'
21
+ end
22
+
23
+ def pretty_print(q)
24
+ q.group(1, "#{self.class.type.name}[", ']') do
25
+ type.field_names.each_with_index do |name, i|
26
+ if i == 0
27
+ q.breakable ''
28
+ else
29
+ q.text ','
30
+ q.breakable ' '
31
+ end
32
+ q.text name.to_s
33
+ q.text ':'
34
+ q.group(1) do
35
+ q.breakable ' '
36
+ q.pp self[name]
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def to_hash
43
+ type.field_names.inject({}) { |h, name| h.update name => self[name] }
44
+ end
45
+
46
+ alias_method :to_h, :to_hash
47
+
48
+ def self.type=(type)
49
+ super(type)
50
+ raise unless type.field_names?
51
+ end
52
+
53
+ def update(fields)
54
+ type[to_hash.merge fields]
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+
17
+ # A private classes used for Product values creation
18
+ module ProductConstructors
19
+
20
+ require 'algebrick/product_constructors/abstract'
21
+ require 'algebrick/product_constructors/basic'
22
+ require 'algebrick/product_constructors/named'
23
+
24
+ end
25
+ end
@@ -0,0 +1,195 @@
1
+ # Copyright 2013 Petr Chalupa <git+algebrick@pitr.ch>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Algebrick
16
+ # Representation of Product and Variant types. The class behaves differently
17
+ # based on #kind.
18
+ #noinspection RubyTooManyMethodsInspection
19
+ class ProductVariant < Type
20
+ # TODO split up into classes or modules
21
+ # TODO aliases for fields, (update matchers)
22
+
23
+ include FieldMethodReaders
24
+ attr_reader :fields, :variants
25
+
26
+ def initialize(name, &definition)
27
+ super(name, &definition)
28
+ @to_be_kind_of = []
29
+ @final_variants = false
30
+ end
31
+
32
+ def set_fields(fields_or_hash)
33
+ raise TypeError, 'can be set only once' if @fields
34
+ @kind = nil
35
+ fields, keys = case fields_or_hash
36
+ when Hash
37
+ [fields_or_hash.values, fields_or_hash.keys]
38
+ when Array
39
+ [fields_or_hash, nil]
40
+ else
41
+ raise ArgumentError
42
+ end
43
+
44
+ add_field_names keys if keys
45
+
46
+ fields.all? { |f| Type! f, Type, Class, Module }
47
+ raise TypeError, 'there is no product with zero fields' unless fields.size > 0
48
+ define_method(:value) { @fields.first } if fields.size == 1
49
+ @fields = fields
50
+ @constructor = Class.new(
51
+ field_names? ? ProductConstructors::Named : ProductConstructors::Basic).
52
+ tap { |c| c.type = self }
53
+ const_set :Constructor, @constructor
54
+ apply_be_kind_of
55
+ self
56
+ end
57
+
58
+ def field_indexes
59
+ @field_indexes or raise TypeError, "field names not defined on #{self}"
60
+ end
61
+
62
+ def field(name)
63
+ fields[field_indexes[name]]
64
+ end
65
+
66
+ def final!
67
+ @final_variants = true
68
+ self
69
+ end
70
+
71
+ def set_variants(*variants)
72
+ raise TypeError, 'can be set only once' if @variants && @final_variants
73
+ @kind = nil
74
+ variants.all? { |v| Type! v, Type, Class }
75
+ @variants ||= []
76
+ @variants += variants
77
+ apply_be_kind_of
78
+ variants.each do |v|
79
+ if v.respond_to? :be_kind_of
80
+ v.be_kind_of self
81
+ else
82
+ v.send :include, self
83
+ end
84
+ end
85
+ self
86
+ end
87
+
88
+ def new(*fields)
89
+ raise TypeError, "#{self} does not have fields" unless @constructor
90
+ @constructor.new *fields
91
+ end
92
+
93
+ alias_method :[], :new
94
+
95
+ def ==(other)
96
+ other.kind_of? ProductVariant and
97
+ variants == other.variants and fields == other.fields
98
+ end
99
+
100
+ def be_kind_of(type)
101
+ @to_be_kind_of << type
102
+ apply_be_kind_of
103
+ end
104
+
105
+ def apply_be_kind_of
106
+ @to_be_kind_of.each do |type|
107
+ @constructor.send :include, type if @constructor
108
+ variants.each { |v| v.be_kind_of type unless v == self } if @variants
109
+ end
110
+ end
111
+
112
+ def call(*field_matchers)
113
+ raise TypeError, "#{self} does not have any fields" unless @fields
114
+ Matchers::Product.new self, *field_matchers
115
+ end
116
+
117
+ def to_m
118
+ case kind
119
+ when :product
120
+ Matchers::Product.new self
121
+ when :product_variant
122
+ Matchers::Variant.new self
123
+ when :variant
124
+ Matchers::Variant.new self
125
+ else
126
+ raise
127
+ end
128
+ end
129
+
130
+ def to_s
131
+ case kind
132
+ when :product
133
+ product_to_s
134
+ when :product_variant
135
+ name + '(' +
136
+ variants.map do |variant|
137
+ if variant == self
138
+ product_to_s
139
+ else
140
+ variant.name
141
+ end
142
+ end.join(' | ') +
143
+ ')'
144
+ when :variant
145
+ "#{name}(#{variants.map(&:name).join ' | '})"
146
+ else
147
+ raise
148
+ end
149
+ end
150
+
151
+ #noinspection RubyCaseWithoutElseBlockInspection
152
+ def kind
153
+ @kind ||= case
154
+ when @fields && !@variants
155
+ :product
156
+ when @fields && @variants
157
+ :product_variant
158
+ when !@fields && @variants
159
+ :variant
160
+ when !@fields && !@variants
161
+ raise TypeError, 'fields or variants have to be set'
162
+ end
163
+ end
164
+
165
+ def assigned_types
166
+ @assigned_types or raise TypeError, "#{self} does not have assigned types"
167
+ end
168
+
169
+ def assigned_types=(assigned_types)
170
+ raise TypeError, "#{self} assigned types already set" if @assigned_types
171
+ @assigned_types = assigned_types
172
+ end
173
+
174
+ private
175
+
176
+ def product_to_s
177
+ fields_str = if field_names?
178
+ field_names.zip(fields).map { |name, field| "#{name}: #{field.name}" }
179
+ else
180
+ fields.map(&:name)
181
+ end
182
+ "#{name}(#{fields_str.join ', '})"
183
+ end
184
+
185
+ def add_field_names(names)
186
+ @field_names = names
187
+ names.all? { |k| Type! k, Symbol }
188
+ dict = @field_indexes =
189
+ Hash.new { |_, k| raise ArgumentError, "unknown field #{k.inspect} in #{self}" }.
190
+ update names.each_with_index.inject({}) { |h, (k, i)| h.update k => i }
191
+ define_method(:[]) { |key| @fields[dict[key]] }
192
+ # TODO use attr_readers to speed things up
193
+ end
194
+ end
195
+ end