qrb 0.1.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.
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +58 -0
- data/LICENCE.md +22 -0
- data/Manifest.txt +11 -0
- data/README.md +118 -0
- data/Rakefile +11 -0
- data/lib/qrb/Q/default.q +29 -0
- data/lib/qrb/data_type.rb +23 -0
- data/lib/qrb/errors.rb +23 -0
- data/lib/qrb/support/attribute.rb +53 -0
- data/lib/qrb/support/collection_type.rb +25 -0
- data/lib/qrb/support/dress_helper.rb +68 -0
- data/lib/qrb/support/heading.rb +57 -0
- data/lib/qrb/support/type_factory.rb +178 -0
- data/lib/qrb/support.rb +5 -0
- data/lib/qrb/syntax/ad_type.rb +25 -0
- data/lib/qrb/syntax/attribute.rb +11 -0
- data/lib/qrb/syntax/builtin_type.rb +13 -0
- data/lib/qrb/syntax/constraint_def.rb +11 -0
- data/lib/qrb/syntax/constraints.rb +18 -0
- data/lib/qrb/syntax/contract.rb +25 -0
- data/lib/qrb/syntax/definitions.rb +14 -0
- data/lib/qrb/syntax/expression.rb +12 -0
- data/lib/qrb/syntax/heading.rb +15 -0
- data/lib/qrb/syntax/lambda_expr.rb +11 -0
- data/lib/qrb/syntax/named_constraint.rb +11 -0
- data/lib/qrb/syntax/q.citrus +195 -0
- data/lib/qrb/syntax/relation_type.rb +11 -0
- data/lib/qrb/syntax/seq_type.rb +12 -0
- data/lib/qrb/syntax/set_type.rb +12 -0
- data/lib/qrb/syntax/sub_type.rb +13 -0
- data/lib/qrb/syntax/support.rb +13 -0
- data/lib/qrb/syntax/system.rb +15 -0
- data/lib/qrb/syntax/tuple_type.rb +11 -0
- data/lib/qrb/syntax/type_def.rb +14 -0
- data/lib/qrb/syntax/type_ref.rb +13 -0
- data/lib/qrb/syntax/union_type.rb +12 -0
- data/lib/qrb/syntax/unnamed_constraint.rb +11 -0
- data/lib/qrb/syntax.rb +42 -0
- data/lib/qrb/system.rb +63 -0
- data/lib/qrb/type/ad_type.rb +111 -0
- data/lib/qrb/type/builtin_type.rb +56 -0
- data/lib/qrb/type/relation_type.rb +81 -0
- data/lib/qrb/type/seq_type.rb +51 -0
- data/lib/qrb/type/set_type.rb +52 -0
- data/lib/qrb/type/sub_type.rb +94 -0
- data/lib/qrb/type/tuple_type.rb +99 -0
- data/lib/qrb/type/union_type.rb +78 -0
- data/lib/qrb/type.rb +63 -0
- data/lib/qrb/version.rb +14 -0
- data/lib/qrb.rb +63 -0
- data/qrb.gemspec +186 -0
- data/spec/acceptance/Q/test_default.rb +96 -0
- data/spec/acceptance/Q/test_parsing.rb +15 -0
- data/spec/acceptance/ad_type/test_in_q.rb +82 -0
- data/spec/acceptance/ad_type/test_in_ruby.rb +60 -0
- data/spec/spec_helper.rb +68 -0
- data/spec/unit/attribute/test_equality.rb +26 -0
- data/spec/unit/attribute/test_fetch_on.rb +50 -0
- data/spec/unit/attribute/test_initialize.rb +13 -0
- data/spec/unit/attribute/test_to_name.rb +10 -0
- data/spec/unit/heading/test_each.rb +28 -0
- data/spec/unit/heading/test_equality.rb +28 -0
- data/spec/unit/heading/test_initialize.rb +36 -0
- data/spec/unit/heading/test_size.rb +30 -0
- data/spec/unit/heading/test_to_name.rb +32 -0
- data/spec/unit/qrb/test_parse.rb +18 -0
- data/spec/unit/syntax/nodes/test_ad_type.rb +94 -0
- data/spec/unit/syntax/nodes/test_attribute.rb +25 -0
- data/spec/unit/syntax/nodes/test_builtin_type.rb +32 -0
- data/spec/unit/syntax/nodes/test_comment.rb +26 -0
- data/spec/unit/syntax/nodes/test_constraint_def.rb +27 -0
- data/spec/unit/syntax/nodes/test_constraints.rb +51 -0
- data/spec/unit/syntax/nodes/test_contract.rb +62 -0
- data/spec/unit/syntax/nodes/test_expression.rb +43 -0
- data/spec/unit/syntax/nodes/test_heading.rb +41 -0
- data/spec/unit/syntax/nodes/test_named_constraint.rb +31 -0
- data/spec/unit/syntax/nodes/test_relation_type.rb +41 -0
- data/spec/unit/syntax/nodes/test_seq_type.rb +24 -0
- data/spec/unit/syntax/nodes/test_set_type.rb +24 -0
- data/spec/unit/syntax/nodes/test_spacing.rb +25 -0
- data/spec/unit/syntax/nodes/test_sub_type.rb +52 -0
- data/spec/unit/syntax/nodes/test_tuple_type.rb +41 -0
- data/spec/unit/syntax/nodes/test_union_type.rb +23 -0
- data/spec/unit/syntax/nodes/test_unnamed_constraint.rb +31 -0
- data/spec/unit/syntax/test_compile_type.rb +22 -0
- data/spec/unit/system/test_add_type.rb +47 -0
- data/spec/unit/system/test_dsl.rb +30 -0
- data/spec/unit/system/test_dup.rb +30 -0
- data/spec/unit/system/test_fetch.rb +42 -0
- data/spec/unit/system/test_get_type.rb +30 -0
- data/spec/unit/system/test_initialize.rb +10 -0
- data/spec/unit/test_qrb.rb +15 -0
- data/spec/unit/type/ad_type/test_default_name.rb +15 -0
- data/spec/unit/type/ad_type/test_dress.rb +55 -0
- data/spec/unit/type/ad_type/test_include.rb +22 -0
- data/spec/unit/type/ad_type/test_initialize.rb +40 -0
- data/spec/unit/type/ad_type/test_name.rb +20 -0
- data/spec/unit/type/builtin_type/test_default_name.rb +12 -0
- data/spec/unit/type/builtin_type/test_dress.rb +33 -0
- data/spec/unit/type/builtin_type/test_equality.rb +26 -0
- data/spec/unit/type/builtin_type/test_include.rb +22 -0
- data/spec/unit/type/builtin_type/test_initialize.rb +12 -0
- data/spec/unit/type/builtin_type/test_name.rb +24 -0
- data/spec/unit/type/relation_type/test_default_name.rb +16 -0
- data/spec/unit/type/relation_type/test_dress.rb +164 -0
- data/spec/unit/type/relation_type/test_equality.rb +32 -0
- data/spec/unit/type/relation_type/test_include.rb +46 -0
- data/spec/unit/type/relation_type/test_initialize.rb +26 -0
- data/spec/unit/type/relation_type/test_name.rb +24 -0
- data/spec/unit/type/seq_type/test_default_name.rb +14 -0
- data/spec/unit/type/seq_type/test_dress.rb +49 -0
- data/spec/unit/type/seq_type/test_equality.rb +26 -0
- data/spec/unit/type/seq_type/test_include.rb +43 -0
- data/spec/unit/type/seq_type/test_initialize.rb +28 -0
- data/spec/unit/type/seq_type/test_name.rb +24 -0
- data/spec/unit/type/set_type/test_default_name.rb +14 -0
- data/spec/unit/type/set_type/test_dress.rb +66 -0
- data/spec/unit/type/set_type/test_equality.rb +26 -0
- data/spec/unit/type/set_type/test_include.rb +43 -0
- data/spec/unit/type/set_type/test_initialize.rb +28 -0
- data/spec/unit/type/set_type/test_name.rb +24 -0
- data/spec/unit/type/sub_type/test_default_name.rb +14 -0
- data/spec/unit/type/sub_type/test_dress.rb +75 -0
- data/spec/unit/type/sub_type/test_equality.rb +34 -0
- data/spec/unit/type/sub_type/test_include.rb +34 -0
- data/spec/unit/type/sub_type/test_initialize.rb +16 -0
- data/spec/unit/type/sub_type/test_name.rb +24 -0
- data/spec/unit/type/tuple_type/test_default_name.rb +14 -0
- data/spec/unit/type/tuple_type/test_dress.rb +112 -0
- data/spec/unit/type/tuple_type/test_equality.rb +32 -0
- data/spec/unit/type/tuple_type/test_include.rb +38 -0
- data/spec/unit/type/tuple_type/test_initialize.rb +30 -0
- data/spec/unit/type/tuple_type/test_name.rb +24 -0
- data/spec/unit/type/union_type/test_default_name.rb +12 -0
- data/spec/unit/type/union_type/test_dress.rb +43 -0
- data/spec/unit/type/union_type/test_equality.rb +30 -0
- data/spec/unit/type/union_type/test_include.rb +28 -0
- data/spec/unit/type/union_type/test_initialize.rb +24 -0
- data/spec/unit/type/union_type/test_name.rb +20 -0
- data/spec/unit/type_factory/dsl/test_adt.rb +54 -0
- data/spec/unit/type_factory/dsl/test_attribute.rb +37 -0
- data/spec/unit/type_factory/dsl/test_attributes.rb +41 -0
- data/spec/unit/type_factory/dsl/test_builtin.rb +45 -0
- data/spec/unit/type_factory/dsl/test_relation.rb +85 -0
- data/spec/unit/type_factory/dsl/test_seq.rb +57 -0
- data/spec/unit/type_factory/dsl/test_set.rb +57 -0
- data/spec/unit/type_factory/dsl/test_subtype.rb +91 -0
- data/spec/unit/type_factory/dsl/test_tuple.rb +73 -0
- data/spec/unit/type_factory/dsl/test_union.rb +81 -0
- data/spec/unit/type_factory/factory/test_builtin.rb +24 -0
- data/spec/unit/type_factory/factory/test_seq_type.rb +44 -0
- data/spec/unit/type_factory/factory/test_set_type.rb +44 -0
- data/spec/unit/type_factory/factory/test_sub_type.rb +53 -0
- data/spec/unit/type_factory/factory/test_tuple_type.rb +43 -0
- data/tasks/gem.rake +73 -0
- data/tasks/test.rake +31 -0
- metadata +344 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
module Qrb
|
|
3
|
+
#
|
|
4
|
+
# The Abtract/Algebraic Data Type is a generator to build user-defined
|
|
5
|
+
# information types in a more abstract way than using sub types. For
|
|
6
|
+
# instance, a Color could be defined as follows:
|
|
7
|
+
#
|
|
8
|
+
# Color := <Rgb> {r: Byte, g: Byte, b: Byte},
|
|
9
|
+
# <Hex> String( s | s =~ /^#[0-9a-f]{6}$/i )
|
|
10
|
+
#
|
|
11
|
+
# Such a declaration does not define a new type by *subtyping* but instead
|
|
12
|
+
# declares so-called possible representations for the color. Here, two
|
|
13
|
+
# possible representations are defined: one is a rgb triple through a Tuple
|
|
14
|
+
# type, the other is an hexadecimal notation through a subset of String.
|
|
15
|
+
#
|
|
16
|
+
# Given an AdType, Q requires special dress/undress function pairs to
|
|
17
|
+
# encode/decode from the type to the concrete representations and vice versa.
|
|
18
|
+
# This pair of functions is called the "information contract".
|
|
19
|
+
#
|
|
20
|
+
# This class allows capturing such information types. For instance,
|
|
21
|
+
#
|
|
22
|
+
# # This is the concrete representation of Q's Color, through a usual
|
|
23
|
+
# # Ruby ADT (we call it ColorImpl here to avoid confusion, but in
|
|
24
|
+
# # practice one would simply call it Color).
|
|
25
|
+
# class ColorImpl
|
|
26
|
+
# # ... your usual color implementation
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# # The RGB info type: {r: Byte, g: Byte, b: Byte}
|
|
30
|
+
# rgb_infotype = TupleType.new(...)
|
|
31
|
+
#
|
|
32
|
+
# # The RGB contract converter, an object that responds to `call` to
|
|
33
|
+
# # convert from a valid Hash[r: Fixnum, ...] to a ColorImpl instance.
|
|
34
|
+
# rgb_contract = ...
|
|
35
|
+
#
|
|
36
|
+
# AdType.new(ColorImpl, rgb: [rgb_infotype, rgb_contract], hex: ...)
|
|
37
|
+
#
|
|
38
|
+
# The ruby ADT class is of course used as concrete representation of the
|
|
39
|
+
# AdType, that is,
|
|
40
|
+
#
|
|
41
|
+
# R(Color) = ColorImpl
|
|
42
|
+
#
|
|
43
|
+
# Accordingly, the `dress` transformation function has the following
|
|
44
|
+
# signature:
|
|
45
|
+
#
|
|
46
|
+
# dress :: Alpha -> Color throws TypeError
|
|
47
|
+
# dress :: Object -> ColorImpl throws TypeError
|
|
48
|
+
#
|
|
49
|
+
class AdType < Type
|
|
50
|
+
|
|
51
|
+
def initialize(ruby_type, contracts, name = nil)
|
|
52
|
+
unless ruby_type.nil? or ruby_type.is_a?(Module)
|
|
53
|
+
raise ArgumentError, "Module expected, got `#{ruby_type}`"
|
|
54
|
+
end
|
|
55
|
+
unless contracts.is_a?(Hash)
|
|
56
|
+
raise ArgumentError, "Hash expected, got `#{contracts}`"
|
|
57
|
+
end
|
|
58
|
+
invalid = contracts.values.reject{|v|
|
|
59
|
+
v.is_a?(Array) and v.size == 2 and v.first.is_a?(Type) and v.last.respond_to?(:call)
|
|
60
|
+
}
|
|
61
|
+
unless invalid.empty?
|
|
62
|
+
raise ArgumentError, "Invalid contracts `#{invalid}`"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
super(name)
|
|
66
|
+
@ruby_type = ruby_type
|
|
67
|
+
@contracts = contracts.freeze
|
|
68
|
+
end
|
|
69
|
+
attr_reader :ruby_type, :contracts
|
|
70
|
+
|
|
71
|
+
def contract_names
|
|
72
|
+
contracts.keys
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def default_name
|
|
76
|
+
(ruby_type && ruby_type.name.to_s) || "Anonymous"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def include?(value)
|
|
80
|
+
ruby_type === value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def dress(value, handler = DressHelper.new)
|
|
84
|
+
# Up should be idempotent with respect to the ADT
|
|
85
|
+
return value if ruby_type and value.is_a?(ruby_type)
|
|
86
|
+
|
|
87
|
+
# Try each contract in turn. Do nothing on TypeError as
|
|
88
|
+
# the next candidate could be the good one! Return the
|
|
89
|
+
# first successfully uped.
|
|
90
|
+
contracts.each_pair do |name, (infotype,upper)|
|
|
91
|
+
|
|
92
|
+
# First make the dress transformation on the information type
|
|
93
|
+
success, uped = handler.just_try do
|
|
94
|
+
infotype.dress(value, handler)
|
|
95
|
+
end
|
|
96
|
+
next unless success
|
|
97
|
+
|
|
98
|
+
# Seems nice, just try to get one stage higher now
|
|
99
|
+
success, uped = handler.just_try(StandardError) do
|
|
100
|
+
upper.call(uped)
|
|
101
|
+
end
|
|
102
|
+
return uped if success
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# No one succeeded, just fail
|
|
107
|
+
handler.failed!(self, value)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end # class AdType
|
|
111
|
+
end # module Qrb
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# A BuiltinType generator allows capuring an information type to a type of
|
|
4
|
+
# the host language, here a Ruby class. For instance,
|
|
5
|
+
#
|
|
6
|
+
# Int := BuiltinType(ruby.Fixnum)
|
|
7
|
+
#
|
|
8
|
+
# The set of values captured by the information type is the same set of
|
|
9
|
+
# values that can be represented by the host type. In the example, `Int`
|
|
10
|
+
# captures the same set of numbers as ruby's Fixnum.
|
|
11
|
+
#
|
|
12
|
+
# The ruby class is used as concrete representation of the information type.
|
|
13
|
+
# In the example:
|
|
14
|
+
#
|
|
15
|
+
# R(Int) = Fixnum
|
|
16
|
+
#
|
|
17
|
+
# Accordingly, the `dress` transformation function has the following signature:
|
|
18
|
+
#
|
|
19
|
+
# dress :: Alpha -> Int throws TypeError
|
|
20
|
+
# dress :: Object -> Fixnum throws TypeError
|
|
21
|
+
#
|
|
22
|
+
class BuiltinType < Type
|
|
23
|
+
|
|
24
|
+
def initialize(ruby_type, name = nil)
|
|
25
|
+
super(name)
|
|
26
|
+
@ruby_type = ruby_type
|
|
27
|
+
end
|
|
28
|
+
attr_reader :ruby_type
|
|
29
|
+
|
|
30
|
+
def default_name
|
|
31
|
+
@ruby_type.name.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def include?(value)
|
|
35
|
+
ruby_type === value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check that `value` is a valid instance of `ruby_type` through `===` or
|
|
39
|
+
# raise an error.
|
|
40
|
+
def dress(value, handler = DressHelper.new)
|
|
41
|
+
handler.failed!(self, value) unless ruby_type===value
|
|
42
|
+
value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ==(other)
|
|
46
|
+
return false unless other.is_a?(BuiltinType)
|
|
47
|
+
other.ruby_type==ruby_type
|
|
48
|
+
end
|
|
49
|
+
alias :eql? :==
|
|
50
|
+
|
|
51
|
+
def hash
|
|
52
|
+
self.class.hash ^ ruby_type.hash
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end # class BuiltinType
|
|
56
|
+
end # module Qrb
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# The Relation type generator allows capturing sets of information facts,
|
|
4
|
+
# i.e. sets of tuples (of same heading). E.g.
|
|
5
|
+
#
|
|
6
|
+
# ColoredPoints = {{ point: Point, color: Color }}
|
|
7
|
+
#
|
|
8
|
+
# This class allows capturing relation types, in a way similar to TupleType:
|
|
9
|
+
#
|
|
10
|
+
# ColoredPoints = RelationType.new( Heading[...] )
|
|
11
|
+
#
|
|
12
|
+
# A ruby Set is used as concrete representation, and will contain hashes
|
|
13
|
+
# that are valid representations of the associated tuple type:
|
|
14
|
+
#
|
|
15
|
+
# R(ColoredPoints) = Set[ R({...}) ] = Set[Hash[...]]
|
|
16
|
+
#
|
|
17
|
+
# Accordingly, the dress transformation function has the signature below.
|
|
18
|
+
# It expects an Enumerable as input and fails if any duplicate is found
|
|
19
|
+
# (after tuple transformation), or if any tuple fails at being transformed.
|
|
20
|
+
#
|
|
21
|
+
# dress :: Alpha -> ColoredPoints throws TypeError
|
|
22
|
+
# dress :: Object -> Set[Hash[...]] throws TypeError
|
|
23
|
+
#
|
|
24
|
+
class RelationType < Type
|
|
25
|
+
|
|
26
|
+
def initialize(heading, name = nil)
|
|
27
|
+
unless heading.is_a?(Heading)
|
|
28
|
+
raise ArgumentError, "Heading expected, got `#{heading}`"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
super(name)
|
|
32
|
+
@heading = heading
|
|
33
|
+
end
|
|
34
|
+
attr_reader :heading
|
|
35
|
+
|
|
36
|
+
def default_name
|
|
37
|
+
"{{#{heading.to_name}}}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def include?(value)
|
|
41
|
+
value.is_a?(Set) && value.all?{|tuple|
|
|
42
|
+
tuple_type.include?(tuple)
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Apply the corresponding TupleType's `dress` to every element of `value`
|
|
47
|
+
# (any enumerable). Return a Set of transformed tuples. Fail if anything
|
|
48
|
+
# goes wrong transforming tuples or if duplicates are found.
|
|
49
|
+
def dress(value, handler = DressHelper.new)
|
|
50
|
+
handler.failed!(self, value) unless value.respond_to?(:each)
|
|
51
|
+
|
|
52
|
+
# Up every tuple and keep results in a Set
|
|
53
|
+
set = Set.new
|
|
54
|
+
handler.iterate(value) do |tuple, index|
|
|
55
|
+
tuple = tuple_type.dress(tuple, handler)
|
|
56
|
+
handler.fail!("Duplicate tuple") if set.include?(tuple)
|
|
57
|
+
set << tuple
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Return built tuples
|
|
61
|
+
set
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ==(other)
|
|
65
|
+
return false unless other.is_a?(RelationType)
|
|
66
|
+
heading == other.heading
|
|
67
|
+
end
|
|
68
|
+
alias :eql? :==
|
|
69
|
+
|
|
70
|
+
def hash
|
|
71
|
+
self.class.hash ^ heading.hash
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def tuple_type
|
|
77
|
+
@tuple_type ||= TupleType.new(heading)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end # class RelationType
|
|
81
|
+
end # module Qrb
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# The Seq type generator allows capturing sequences of values. For example,
|
|
4
|
+
# a (implicitely temporal) series of temperature measures could be written
|
|
5
|
+
# as:
|
|
6
|
+
#
|
|
7
|
+
# Measures = [Temperature]
|
|
8
|
+
#
|
|
9
|
+
# This class allows capturing those sequence types, e.g.:
|
|
10
|
+
#
|
|
11
|
+
# Temperature = BuiltinType.new(Float)
|
|
12
|
+
# Measures = SeqType.new(Temperature)
|
|
13
|
+
#
|
|
14
|
+
# An array of values is used as concrete representation for such sequences:
|
|
15
|
+
#
|
|
16
|
+
# R(Measures) = Array[R(Temperature)] = Array[Float]
|
|
17
|
+
#
|
|
18
|
+
# Accordingly, the `dress` transformation function has the signature below.
|
|
19
|
+
# It expects it's Alpha/Object argument to be a object responding to
|
|
20
|
+
# `each` (with the ruby idiomatic semantics that such a `each` returns
|
|
21
|
+
# an Enumerator when invoked without block).
|
|
22
|
+
#
|
|
23
|
+
# dress :: Alpha -> Measures throws TypeError
|
|
24
|
+
# dress :: Object -> Array[Float] throws TypeError
|
|
25
|
+
#
|
|
26
|
+
class SeqType < Type
|
|
27
|
+
include CollectionType
|
|
28
|
+
|
|
29
|
+
def include?(value)
|
|
30
|
+
value.is_a?(::Array) and value.all?{|v| elm_type.include?(v) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Apply the element type's `dress` transformation to each element of
|
|
34
|
+
# `value` (expected to respond to `each`). Return converted values in a
|
|
35
|
+
# ruby Array.
|
|
36
|
+
def dress(value, handler = DressHelper.new)
|
|
37
|
+
handler.failed!(self, value) unless value.respond_to?(:each)
|
|
38
|
+
|
|
39
|
+
array = []
|
|
40
|
+
handler.iterate(value) do |elm, index|
|
|
41
|
+
array << elm_type.dress(elm, handler)
|
|
42
|
+
end
|
|
43
|
+
array
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def default_name
|
|
47
|
+
"[#{elm_type.name}]"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end # class SeqType
|
|
51
|
+
end # module Qrb
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# The Set type generator allows capturing an unordered set of values (i.e.
|
|
4
|
+
# with no duplicates). For example, a set of emails could be captured with:
|
|
5
|
+
#
|
|
6
|
+
# Adresses = {Email}
|
|
7
|
+
#
|
|
8
|
+
# This class allows capturing those set types, e.g.:
|
|
9
|
+
#
|
|
10
|
+
# Email = BuiltinType.new(String)
|
|
11
|
+
# Adresses = SetType.new(Email)
|
|
12
|
+
#
|
|
13
|
+
# A ruby Set of values is used as concrete representation for such sets:
|
|
14
|
+
#
|
|
15
|
+
# R(Adresses) = Set[R(Email)] = Set[String]
|
|
16
|
+
#
|
|
17
|
+
# Accordingly, the `dress` transformation function has the signature below.
|
|
18
|
+
# It expects it's Alpha/Object argument to be a object responding to
|
|
19
|
+
# `each` (with the ruby idiomatic semantics that such a `each` returns
|
|
20
|
+
# an Enumerator when invoked without block).
|
|
21
|
+
#
|
|
22
|
+
# dress :: Alpha -> Adresses throws TypeError
|
|
23
|
+
# dress :: Object -> Set[String] throws TypeError
|
|
24
|
+
#
|
|
25
|
+
class SetType < Type
|
|
26
|
+
include CollectionType
|
|
27
|
+
|
|
28
|
+
def default_name
|
|
29
|
+
"{#{elm_type.name}}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def include?(value)
|
|
33
|
+
value.is_a?(::Set) and value.all?{|v| elm_type.include?(v) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Apply the element type's `dress` transformation to each element of
|
|
37
|
+
# `value` (expected to respond to `each`). Return converted values in a
|
|
38
|
+
# ruby Set.
|
|
39
|
+
def dress(value, handler = DressHelper.new)
|
|
40
|
+
handler.failed!(self, value) unless value.respond_to?(:each)
|
|
41
|
+
|
|
42
|
+
set = Set.new
|
|
43
|
+
handler.iterate(value) do |elm, index|
|
|
44
|
+
elm = elm_type.dress(elm, handler)
|
|
45
|
+
handler.fail!("Duplicate value `#{elm}`") if set.include?(elm)
|
|
46
|
+
set << elm
|
|
47
|
+
end
|
|
48
|
+
set
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end # class SetType
|
|
52
|
+
end # module Qrb
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# A sub type generator, through specialization by constraints.
|
|
4
|
+
#
|
|
5
|
+
# A sub type captures a subset of the values of a super type, through a
|
|
6
|
+
# constraint. For instance, a Byte type can be defined as a subset of all
|
|
7
|
+
# integers, as follows:
|
|
8
|
+
#
|
|
9
|
+
# Byte := Integer( i | i >= 0 and i <= 255 )
|
|
10
|
+
#
|
|
11
|
+
# This class allows defining such sub types with multiple named constraints.
|
|
12
|
+
# For instance,
|
|
13
|
+
#
|
|
14
|
+
# Int = BuiltinType.new(Integer)
|
|
15
|
+
# Byte = SubType.new(Int, positive: ->(i){ i >= 0 },
|
|
16
|
+
# small: ->(i){ i <= 255 })
|
|
17
|
+
#
|
|
18
|
+
# The concrete representation of the super type is kept as representation
|
|
19
|
+
# of the sub type. In other words:
|
|
20
|
+
#
|
|
21
|
+
# R(Byte) = R(Int) = Fixnum
|
|
22
|
+
#
|
|
23
|
+
# Accordingly, the `dress` transformation function has the following signature:
|
|
24
|
+
#
|
|
25
|
+
# dress :: Alpha -> Byte throws TypeError
|
|
26
|
+
# dress :: Object -> Fixnum throws TypeError
|
|
27
|
+
#
|
|
28
|
+
class SubType < Type
|
|
29
|
+
|
|
30
|
+
DEFAULT_CONSTRAINT_NAMES = [:default, :predicate].freeze
|
|
31
|
+
|
|
32
|
+
def initialize(super_type, constraints, name = nil)
|
|
33
|
+
unless super_type.is_a?(Type)
|
|
34
|
+
raise ArgumentError, "Qrb::Type expected, got #{super_type}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
unless constraints.is_a?(Hash)
|
|
38
|
+
raise ArgumentError, "Hash expected for constraints, got #{constraints}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
super(name)
|
|
42
|
+
@super_type, @constraints = super_type, constraints.freeze
|
|
43
|
+
end
|
|
44
|
+
attr_reader :super_type, :constraints
|
|
45
|
+
|
|
46
|
+
def default_name
|
|
47
|
+
constraints.keys.first.to_s.capitalize
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def include?(value)
|
|
51
|
+
super_type.include?(value) && constraints.all?{|_,c| c===value }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check that `value` can be uped through the supertype, then verify all
|
|
55
|
+
# constraints. Raise an error if anything goes wrong.
|
|
56
|
+
def dress(value, handler = DressHelper.new)
|
|
57
|
+
# Check that the supertype is able to dress the value.
|
|
58
|
+
# Rewrite and set cause to any encountered TypeError.
|
|
59
|
+
uped = handler.try(self, value) do
|
|
60
|
+
super_type.dress(value, handler)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check each constraint in turn
|
|
64
|
+
constraints.each_pair do |name, constraint|
|
|
65
|
+
next if constraint===uped
|
|
66
|
+
msg = handler.default_error_message(self, value)
|
|
67
|
+
msg << " (not #{name})" unless default_constraint?(name)
|
|
68
|
+
handler.fail!(msg)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# seems good, return the uped value
|
|
72
|
+
uped
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ==(other)
|
|
76
|
+
return false unless other.is_a?(SubType)
|
|
77
|
+
other.super_type == super_type and \
|
|
78
|
+
set_equal?(constraints.values, other.constraints.values)
|
|
79
|
+
end
|
|
80
|
+
alias :eql? :==
|
|
81
|
+
|
|
82
|
+
def hash
|
|
83
|
+
self.class.hash ^ super_type.hash ^ set_hash(constraints.values)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def default_constraint?(name)
|
|
89
|
+
DEFAULT_CONSTRAINT_NAMES.include?(name) or \
|
|
90
|
+
name.to_s.capitalize == self.name
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end # class BuiltinType
|
|
94
|
+
end # module Qrb
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# The Tuple type generator allows capturing information *facts*. For
|
|
4
|
+
# instance, a Point type could be defined as follows:
|
|
5
|
+
#
|
|
6
|
+
# Point = {r: Length, theta: Angle}
|
|
7
|
+
#
|
|
8
|
+
# This class allows capturing those information types, as in:
|
|
9
|
+
#
|
|
10
|
+
# Length = BuiltinType.new(Fixnum)
|
|
11
|
+
# Angle = BuiltinType.new(Float)
|
|
12
|
+
# Point = TupleType.new(Heading.new([
|
|
13
|
+
# Attribute.new(:r, Length),
|
|
14
|
+
# Attribute.new(:theta, Angle)
|
|
15
|
+
# ]))
|
|
16
|
+
#
|
|
17
|
+
# A Hash with Symbol as keys is used as concrete ruby representation for
|
|
18
|
+
# tuples. The values map to the concrete representations of each attribute
|
|
19
|
+
# type:
|
|
20
|
+
#
|
|
21
|
+
# R(Point) = Hash[r: R(Length), theta: R(Angle)]
|
|
22
|
+
# = Hash[r: Fixnum, theta: Float]
|
|
23
|
+
#
|
|
24
|
+
# Accordingly, the `dress` transformation function has the signature below.
|
|
25
|
+
# It expects it's Alpha/Object argument to be a Hash with all and only the
|
|
26
|
+
# expected keys (either as Symbols or Strings). The `dress` function
|
|
27
|
+
# applies on every attribute value according to their respective type.
|
|
28
|
+
#
|
|
29
|
+
# dress :: Alpha -> Point throws TypeError
|
|
30
|
+
# dress :: Object -> Hash[r: Fixnum, theta: Float] throws TypeError
|
|
31
|
+
#
|
|
32
|
+
class TupleType < Type
|
|
33
|
+
|
|
34
|
+
def initialize(heading, name = nil)
|
|
35
|
+
unless heading.is_a?(Heading)
|
|
36
|
+
raise ArgumentError, "Heading expected, got `#{heading}`"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
super(name)
|
|
40
|
+
@heading = heading
|
|
41
|
+
end
|
|
42
|
+
attr_reader :heading
|
|
43
|
+
|
|
44
|
+
def default_name
|
|
45
|
+
"{#{heading.to_name}}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def include?(value)
|
|
49
|
+
return false unless value.is_a?(Hash)
|
|
50
|
+
return false if value.size > heading.size
|
|
51
|
+
heading.all? do |attribute|
|
|
52
|
+
attr_val = value.fetch(attribute.name){
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
attribute.type.include?(attr_val)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Convert `value` (supposed to be Hash) to a Tuple, by checking attributes
|
|
60
|
+
# and applying `dress` on them in turn. Raise an error if any attribute
|
|
61
|
+
# is missing or unrecognized, as well as if any sub transformation fails.
|
|
62
|
+
def dress(value, handler = DressHelper.new)
|
|
63
|
+
handler.failed!(self, value) unless value.is_a?(Hash)
|
|
64
|
+
|
|
65
|
+
# Uped values, i.e. tuple under construction
|
|
66
|
+
uped = {}
|
|
67
|
+
|
|
68
|
+
# Check the tuple arity and fail fast if extra attributes
|
|
69
|
+
# (missing attributes are handled just after)
|
|
70
|
+
if value.size > heading.size
|
|
71
|
+
extra = value.keys.map(&:to_s) - heading.map{|attr| attr.name.to_s }
|
|
72
|
+
handler.fail!("Unrecognized attribute `#{extra.first}`")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Up each attribute in turn now. Fail on missing ones.
|
|
76
|
+
heading.each do |attribute|
|
|
77
|
+
val = attribute.fetch_on(value) do
|
|
78
|
+
handler.fail!("Missing attribute `#{attribute.name}`")
|
|
79
|
+
end
|
|
80
|
+
handler.deeper(attribute.name) do
|
|
81
|
+
uped[attribute.name] = attribute.type.dress(val, handler)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
uped
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ==(other)
|
|
89
|
+
return false unless other.is_a?(TupleType)
|
|
90
|
+
heading == other.heading
|
|
91
|
+
end
|
|
92
|
+
alias :eql? :==
|
|
93
|
+
|
|
94
|
+
def hash
|
|
95
|
+
self.class.hash ^ heading.hash
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end # class TupleType
|
|
99
|
+
end # module Qrb
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
module Qrb
|
|
3
|
+
#
|
|
4
|
+
# A union type (aka algebraic type) allows capturing information types
|
|
5
|
+
# through generalization/disjunction. For instance,
|
|
6
|
+
#
|
|
7
|
+
# Numeric = Int|Real
|
|
8
|
+
#
|
|
9
|
+
# This class allows capturing such union types, as follows:
|
|
10
|
+
#
|
|
11
|
+
# Int = BuiltinType.new(Fixnum)
|
|
12
|
+
# Real = BuiltinType.new(Float)
|
|
13
|
+
# Numeric = UnionType.new([ Int, Real ])
|
|
14
|
+
#
|
|
15
|
+
# When transforming a value through `dress`, the different candidate types
|
|
16
|
+
# are tried in specified order. The first one that succeeds at building the
|
|
17
|
+
# value ends the process and the value is simply returned. Accordingly,
|
|
18
|
+
# the concrete representation will be
|
|
19
|
+
#
|
|
20
|
+
# R(Numeric) = R(Int) ^ R(Real) = Fixnum ^ Float = Numeric
|
|
21
|
+
#
|
|
22
|
+
# where `^` denotes the `least common super type` operator on ruby classes.
|
|
23
|
+
#
|
|
24
|
+
# Accordingly, the `dress` transformation function has the following
|
|
25
|
+
# signature:
|
|
26
|
+
#
|
|
27
|
+
# dress :: Alpha -> Numeric throws TypeError
|
|
28
|
+
# dress :: Object -> Numeric throws TypeError
|
|
29
|
+
#
|
|
30
|
+
class UnionType < Type
|
|
31
|
+
|
|
32
|
+
def initialize(candidates, name = nil)
|
|
33
|
+
unless candidates.all?{|c| c.is_a?(Type) }
|
|
34
|
+
raise ArgumentError, "[Qrb::Type] expected, got #{candidates}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
super(name)
|
|
38
|
+
@candidates = candidates.freeze
|
|
39
|
+
end
|
|
40
|
+
attr_reader :candidates
|
|
41
|
+
|
|
42
|
+
def include?(value)
|
|
43
|
+
candidates.any?{|c| c.include?(value) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Invoke `dress` on each candidate type in turn. Return the value
|
|
47
|
+
# returned by the first one that does not fail. Fail with an TypeError if no
|
|
48
|
+
# candidate succeeds at tranforming `value`.
|
|
49
|
+
def dress(value, handler = DressHelper.new)
|
|
50
|
+
|
|
51
|
+
# Do nothing on TypeError as the next candidate could be the good one!
|
|
52
|
+
candidates.each do |c|
|
|
53
|
+
success, uped = handler.just_try do
|
|
54
|
+
c.dress(value, handler)
|
|
55
|
+
end
|
|
56
|
+
return uped if success
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# No one succeed, just fail
|
|
60
|
+
handler.failed!(self, value)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def default_name
|
|
64
|
+
candidates.map(&:name).join('|')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ==(other)
|
|
68
|
+
return false unless other.is_a?(UnionType)
|
|
69
|
+
set_equal?(candidates, other.candidates)
|
|
70
|
+
end
|
|
71
|
+
alias :eql? :==
|
|
72
|
+
|
|
73
|
+
def hash
|
|
74
|
+
self.class.hash ^ set_hash(self.candidates)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end # class UnionType
|
|
78
|
+
end # module Qrb
|
data/lib/qrb/type.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Qrb
|
|
2
|
+
#
|
|
3
|
+
# Abstract class for Q type (generators).
|
|
4
|
+
#
|
|
5
|
+
class Type
|
|
6
|
+
|
|
7
|
+
def initialize(name)
|
|
8
|
+
unless name.nil? or name.is_a?(String)
|
|
9
|
+
raise ArgumentError, "String expected for type name, got `#{name}`"
|
|
10
|
+
end
|
|
11
|
+
@name = name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def name
|
|
15
|
+
@name || default_name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def name=(n)
|
|
19
|
+
raise Error, "Name already set to `#{@name}`" unless @name.nil?
|
|
20
|
+
@name = n
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
name.to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Check if `value` belongs to this type. Returns true if it's the case,
|
|
28
|
+
# false otherwise.
|
|
29
|
+
#
|
|
30
|
+
# For belonging to the type, `value` MUST be a valid representation, not
|
|
31
|
+
# an 'approximation' or some 'similar' representation. In particular,
|
|
32
|
+
# returning true means that no dressing is required for using `value` as a
|
|
33
|
+
# proper one. Similarly, the method MUST always return true on a value
|
|
34
|
+
# directly obtained through `dress`.
|
|
35
|
+
#
|
|
36
|
+
def include?(value)
|
|
37
|
+
raise NotImplementedError, "Missing #{self.class.name}#include?"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def dress(*args)
|
|
41
|
+
raise NotImplementedError, "Missing #{self.class.name}#dress"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
def set_equal?(s1, s2)
|
|
47
|
+
s1.size == s2.size and (s1-s2).empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_hash(arg)
|
|
51
|
+
arg.map(&:hash).reduce(:^)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end # class Type
|
|
55
|
+
end # module Qrb
|
|
56
|
+
require_relative 'type/builtin_type'
|
|
57
|
+
require_relative 'type/union_type'
|
|
58
|
+
require_relative 'type/sub_type'
|
|
59
|
+
require_relative 'type/seq_type'
|
|
60
|
+
require_relative 'type/set_type'
|
|
61
|
+
require_relative 'type/tuple_type'
|
|
62
|
+
require_relative 'type/relation_type'
|
|
63
|
+
require_relative 'type/ad_type'
|