qrb 0.1.0

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