algebrick 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|