algebrick 0.4.0 → 0.5.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.
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