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.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/README_FULL.md +13 -21
- data/VERSION +1 -1
- data/doc/actor.rb +21 -0
- data/doc/data.in.rb +99 -0
- data/doc/data.out.rb +103 -0
- data/doc/extending_behavior.in.rb +61 -0
- data/doc/extending_behavior.out.rb +62 -0
- data/doc/format.rb +75 -0
- data/doc/init.rb +1 -0
- data/doc/json.in.rb +39 -0
- data/doc/json.out.rb +43 -0
- data/doc/null.in.rb +36 -0
- data/doc/null.out.rb +40 -0
- data/doc/parametrized.in.rb +37 -0
- data/doc/parametrized.out.rb +41 -0
- data/doc/pattern_matching.in.rb +116 -0
- data/doc/pattern_matching.out.rb +122 -0
- data/doc/quick_example.in.rb +27 -0
- data/doc/quick_example.out.rb +27 -0
- data/doc/tree1.in.rb +10 -0
- data/doc/tree1.out.rb +10 -0
- data/doc/type_def.in.rb +21 -0
- data/doc/type_def.out.rb +21 -0
- data/doc/values.in.rb +52 -0
- data/doc/values.out.rb +58 -0
- data/lib/algebrick/atom.rb +49 -0
- data/lib/algebrick/dsl.rb +104 -0
- data/lib/algebrick/field_method_readers.rb +43 -0
- data/lib/algebrick/matcher_delegations.rb +45 -0
- data/lib/algebrick/matchers/abstract.rb +127 -0
- data/lib/algebrick/matchers/abstract_logic.rb +38 -0
- data/lib/algebrick/matchers/and.rb +29 -0
- data/lib/algebrick/matchers/any.rb +37 -0
- data/lib/algebrick/matchers/array.rb +57 -0
- data/lib/algebrick/matchers/atom.rb +28 -0
- data/lib/algebrick/matchers/not.rb +44 -0
- data/lib/algebrick/matchers/or.rb +51 -0
- data/lib/algebrick/matchers/product.rb +73 -0
- data/lib/algebrick/matchers/variant.rb +29 -0
- data/lib/algebrick/matchers/wrapper.rb +57 -0
- data/lib/algebrick/matchers.rb +31 -0
- data/lib/algebrick/matching.rb +62 -0
- data/lib/algebrick/parametrized_type.rb +122 -0
- data/lib/algebrick/product_constructors/abstract.rb +70 -0
- data/lib/algebrick/product_constructors/basic.rb +47 -0
- data/lib/algebrick/product_constructors/named.rb +58 -0
- data/lib/algebrick/product_constructors.rb +25 -0
- data/lib/algebrick/product_variant.rb +195 -0
- data/lib/algebrick/reclude.rb +39 -0
- data/lib/algebrick/serializer.rb +129 -0
- data/lib/algebrick/serializers.rb +25 -0
- data/lib/algebrick/type.rb +61 -0
- data/lib/algebrick/type_check.rb +58 -0
- data/lib/algebrick/types.rb +59 -0
- data/lib/algebrick/value.rb +41 -0
- data/lib/algebrick.rb +14 -1170
- data/spec/algebrick_test.rb +708 -0
- 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
|