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.
Files changed (159) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile +12 -0
  3. data/Gemfile.lock +58 -0
  4. data/LICENCE.md +22 -0
  5. data/Manifest.txt +11 -0
  6. data/README.md +118 -0
  7. data/Rakefile +11 -0
  8. data/lib/qrb/Q/default.q +29 -0
  9. data/lib/qrb/data_type.rb +23 -0
  10. data/lib/qrb/errors.rb +23 -0
  11. data/lib/qrb/support/attribute.rb +53 -0
  12. data/lib/qrb/support/collection_type.rb +25 -0
  13. data/lib/qrb/support/dress_helper.rb +68 -0
  14. data/lib/qrb/support/heading.rb +57 -0
  15. data/lib/qrb/support/type_factory.rb +178 -0
  16. data/lib/qrb/support.rb +5 -0
  17. data/lib/qrb/syntax/ad_type.rb +25 -0
  18. data/lib/qrb/syntax/attribute.rb +11 -0
  19. data/lib/qrb/syntax/builtin_type.rb +13 -0
  20. data/lib/qrb/syntax/constraint_def.rb +11 -0
  21. data/lib/qrb/syntax/constraints.rb +18 -0
  22. data/lib/qrb/syntax/contract.rb +25 -0
  23. data/lib/qrb/syntax/definitions.rb +14 -0
  24. data/lib/qrb/syntax/expression.rb +12 -0
  25. data/lib/qrb/syntax/heading.rb +15 -0
  26. data/lib/qrb/syntax/lambda_expr.rb +11 -0
  27. data/lib/qrb/syntax/named_constraint.rb +11 -0
  28. data/lib/qrb/syntax/q.citrus +195 -0
  29. data/lib/qrb/syntax/relation_type.rb +11 -0
  30. data/lib/qrb/syntax/seq_type.rb +12 -0
  31. data/lib/qrb/syntax/set_type.rb +12 -0
  32. data/lib/qrb/syntax/sub_type.rb +13 -0
  33. data/lib/qrb/syntax/support.rb +13 -0
  34. data/lib/qrb/syntax/system.rb +15 -0
  35. data/lib/qrb/syntax/tuple_type.rb +11 -0
  36. data/lib/qrb/syntax/type_def.rb +14 -0
  37. data/lib/qrb/syntax/type_ref.rb +13 -0
  38. data/lib/qrb/syntax/union_type.rb +12 -0
  39. data/lib/qrb/syntax/unnamed_constraint.rb +11 -0
  40. data/lib/qrb/syntax.rb +42 -0
  41. data/lib/qrb/system.rb +63 -0
  42. data/lib/qrb/type/ad_type.rb +111 -0
  43. data/lib/qrb/type/builtin_type.rb +56 -0
  44. data/lib/qrb/type/relation_type.rb +81 -0
  45. data/lib/qrb/type/seq_type.rb +51 -0
  46. data/lib/qrb/type/set_type.rb +52 -0
  47. data/lib/qrb/type/sub_type.rb +94 -0
  48. data/lib/qrb/type/tuple_type.rb +99 -0
  49. data/lib/qrb/type/union_type.rb +78 -0
  50. data/lib/qrb/type.rb +63 -0
  51. data/lib/qrb/version.rb +14 -0
  52. data/lib/qrb.rb +63 -0
  53. data/qrb.gemspec +186 -0
  54. data/spec/acceptance/Q/test_default.rb +96 -0
  55. data/spec/acceptance/Q/test_parsing.rb +15 -0
  56. data/spec/acceptance/ad_type/test_in_q.rb +82 -0
  57. data/spec/acceptance/ad_type/test_in_ruby.rb +60 -0
  58. data/spec/spec_helper.rb +68 -0
  59. data/spec/unit/attribute/test_equality.rb +26 -0
  60. data/spec/unit/attribute/test_fetch_on.rb +50 -0
  61. data/spec/unit/attribute/test_initialize.rb +13 -0
  62. data/spec/unit/attribute/test_to_name.rb +10 -0
  63. data/spec/unit/heading/test_each.rb +28 -0
  64. data/spec/unit/heading/test_equality.rb +28 -0
  65. data/spec/unit/heading/test_initialize.rb +36 -0
  66. data/spec/unit/heading/test_size.rb +30 -0
  67. data/spec/unit/heading/test_to_name.rb +32 -0
  68. data/spec/unit/qrb/test_parse.rb +18 -0
  69. data/spec/unit/syntax/nodes/test_ad_type.rb +94 -0
  70. data/spec/unit/syntax/nodes/test_attribute.rb +25 -0
  71. data/spec/unit/syntax/nodes/test_builtin_type.rb +32 -0
  72. data/spec/unit/syntax/nodes/test_comment.rb +26 -0
  73. data/spec/unit/syntax/nodes/test_constraint_def.rb +27 -0
  74. data/spec/unit/syntax/nodes/test_constraints.rb +51 -0
  75. data/spec/unit/syntax/nodes/test_contract.rb +62 -0
  76. data/spec/unit/syntax/nodes/test_expression.rb +43 -0
  77. data/spec/unit/syntax/nodes/test_heading.rb +41 -0
  78. data/spec/unit/syntax/nodes/test_named_constraint.rb +31 -0
  79. data/spec/unit/syntax/nodes/test_relation_type.rb +41 -0
  80. data/spec/unit/syntax/nodes/test_seq_type.rb +24 -0
  81. data/spec/unit/syntax/nodes/test_set_type.rb +24 -0
  82. data/spec/unit/syntax/nodes/test_spacing.rb +25 -0
  83. data/spec/unit/syntax/nodes/test_sub_type.rb +52 -0
  84. data/spec/unit/syntax/nodes/test_tuple_type.rb +41 -0
  85. data/spec/unit/syntax/nodes/test_union_type.rb +23 -0
  86. data/spec/unit/syntax/nodes/test_unnamed_constraint.rb +31 -0
  87. data/spec/unit/syntax/test_compile_type.rb +22 -0
  88. data/spec/unit/system/test_add_type.rb +47 -0
  89. data/spec/unit/system/test_dsl.rb +30 -0
  90. data/spec/unit/system/test_dup.rb +30 -0
  91. data/spec/unit/system/test_fetch.rb +42 -0
  92. data/spec/unit/system/test_get_type.rb +30 -0
  93. data/spec/unit/system/test_initialize.rb +10 -0
  94. data/spec/unit/test_qrb.rb +15 -0
  95. data/spec/unit/type/ad_type/test_default_name.rb +15 -0
  96. data/spec/unit/type/ad_type/test_dress.rb +55 -0
  97. data/spec/unit/type/ad_type/test_include.rb +22 -0
  98. data/spec/unit/type/ad_type/test_initialize.rb +40 -0
  99. data/spec/unit/type/ad_type/test_name.rb +20 -0
  100. data/spec/unit/type/builtin_type/test_default_name.rb +12 -0
  101. data/spec/unit/type/builtin_type/test_dress.rb +33 -0
  102. data/spec/unit/type/builtin_type/test_equality.rb +26 -0
  103. data/spec/unit/type/builtin_type/test_include.rb +22 -0
  104. data/spec/unit/type/builtin_type/test_initialize.rb +12 -0
  105. data/spec/unit/type/builtin_type/test_name.rb +24 -0
  106. data/spec/unit/type/relation_type/test_default_name.rb +16 -0
  107. data/spec/unit/type/relation_type/test_dress.rb +164 -0
  108. data/spec/unit/type/relation_type/test_equality.rb +32 -0
  109. data/spec/unit/type/relation_type/test_include.rb +46 -0
  110. data/spec/unit/type/relation_type/test_initialize.rb +26 -0
  111. data/spec/unit/type/relation_type/test_name.rb +24 -0
  112. data/spec/unit/type/seq_type/test_default_name.rb +14 -0
  113. data/spec/unit/type/seq_type/test_dress.rb +49 -0
  114. data/spec/unit/type/seq_type/test_equality.rb +26 -0
  115. data/spec/unit/type/seq_type/test_include.rb +43 -0
  116. data/spec/unit/type/seq_type/test_initialize.rb +28 -0
  117. data/spec/unit/type/seq_type/test_name.rb +24 -0
  118. data/spec/unit/type/set_type/test_default_name.rb +14 -0
  119. data/spec/unit/type/set_type/test_dress.rb +66 -0
  120. data/spec/unit/type/set_type/test_equality.rb +26 -0
  121. data/spec/unit/type/set_type/test_include.rb +43 -0
  122. data/spec/unit/type/set_type/test_initialize.rb +28 -0
  123. data/spec/unit/type/set_type/test_name.rb +24 -0
  124. data/spec/unit/type/sub_type/test_default_name.rb +14 -0
  125. data/spec/unit/type/sub_type/test_dress.rb +75 -0
  126. data/spec/unit/type/sub_type/test_equality.rb +34 -0
  127. data/spec/unit/type/sub_type/test_include.rb +34 -0
  128. data/spec/unit/type/sub_type/test_initialize.rb +16 -0
  129. data/spec/unit/type/sub_type/test_name.rb +24 -0
  130. data/spec/unit/type/tuple_type/test_default_name.rb +14 -0
  131. data/spec/unit/type/tuple_type/test_dress.rb +112 -0
  132. data/spec/unit/type/tuple_type/test_equality.rb +32 -0
  133. data/spec/unit/type/tuple_type/test_include.rb +38 -0
  134. data/spec/unit/type/tuple_type/test_initialize.rb +30 -0
  135. data/spec/unit/type/tuple_type/test_name.rb +24 -0
  136. data/spec/unit/type/union_type/test_default_name.rb +12 -0
  137. data/spec/unit/type/union_type/test_dress.rb +43 -0
  138. data/spec/unit/type/union_type/test_equality.rb +30 -0
  139. data/spec/unit/type/union_type/test_include.rb +28 -0
  140. data/spec/unit/type/union_type/test_initialize.rb +24 -0
  141. data/spec/unit/type/union_type/test_name.rb +20 -0
  142. data/spec/unit/type_factory/dsl/test_adt.rb +54 -0
  143. data/spec/unit/type_factory/dsl/test_attribute.rb +37 -0
  144. data/spec/unit/type_factory/dsl/test_attributes.rb +41 -0
  145. data/spec/unit/type_factory/dsl/test_builtin.rb +45 -0
  146. data/spec/unit/type_factory/dsl/test_relation.rb +85 -0
  147. data/spec/unit/type_factory/dsl/test_seq.rb +57 -0
  148. data/spec/unit/type_factory/dsl/test_set.rb +57 -0
  149. data/spec/unit/type_factory/dsl/test_subtype.rb +91 -0
  150. data/spec/unit/type_factory/dsl/test_tuple.rb +73 -0
  151. data/spec/unit/type_factory/dsl/test_union.rb +81 -0
  152. data/spec/unit/type_factory/factory/test_builtin.rb +24 -0
  153. data/spec/unit/type_factory/factory/test_seq_type.rb +44 -0
  154. data/spec/unit/type_factory/factory/test_set_type.rb +44 -0
  155. data/spec/unit/type_factory/factory/test_sub_type.rb +53 -0
  156. data/spec/unit/type_factory/factory/test_tuple_type.rb +43 -0
  157. data/tasks/gem.rake +73 -0
  158. data/tasks/test.rake +31 -0
  159. 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'
@@ -0,0 +1,14 @@
1
+ module Qrb
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ def self.to_s
9
+ [ MAJOR, MINOR, TINY ].join('.')
10
+ end
11
+
12
+ end
13
+ VERSION = Version.to_s
14
+ end