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
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 0.1.0 / 2014-03-03
2
+
3
+ * Enhancements
4
+
5
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "citrus", "~> 2.4"
4
+
5
+ group :development do
6
+ gem "rake", "~> 10.0"
7
+ gem "rspec", "~> 2.14"
8
+ gem "cucumber", "~> 1.3"
9
+ gem "path", "~> 1.3"
10
+ gem "awesome_print", "~> 1.2"
11
+ gem 'coveralls', require: false
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ awesome_print (1.2.0)
5
+ builder (3.2.2)
6
+ citrus (2.4.1)
7
+ coveralls (0.7.0)
8
+ multi_json (~> 1.3)
9
+ rest-client
10
+ simplecov (>= 0.7)
11
+ term-ansicolor
12
+ thor
13
+ cucumber (1.3.8)
14
+ builder (>= 2.1.2)
15
+ diff-lcs (>= 1.1.3)
16
+ gherkin (~> 2.12.1)
17
+ multi_json (>= 1.7.5, < 2.0)
18
+ multi_test (>= 0.0.2)
19
+ diff-lcs (1.2.5)
20
+ docile (1.1.3)
21
+ gherkin (2.12.2)
22
+ multi_json (~> 1.3)
23
+ mime-types (2.1)
24
+ multi_json (1.8.4)
25
+ multi_test (0.0.2)
26
+ path (1.3.3)
27
+ rake (10.1.1)
28
+ rest-client (1.6.7)
29
+ mime-types (>= 1.16)
30
+ rspec (2.14.1)
31
+ rspec-core (~> 2.14.0)
32
+ rspec-expectations (~> 2.14.0)
33
+ rspec-mocks (~> 2.14.0)
34
+ rspec-core (2.14.7)
35
+ rspec-expectations (2.14.4)
36
+ diff-lcs (>= 1.1.3, < 2.0)
37
+ rspec-mocks (2.14.4)
38
+ simplecov (0.8.2)
39
+ docile (~> 1.1.0)
40
+ multi_json
41
+ simplecov-html (~> 0.8.0)
42
+ simplecov-html (0.8.0)
43
+ term-ansicolor (1.3.0)
44
+ tins (~> 1.0)
45
+ thor (0.18.1)
46
+ tins (1.0.0)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ awesome_print (~> 1.2)
53
+ citrus (~> 2.4)
54
+ coveralls
55
+ cucumber (~> 1.3)
56
+ path (~> 1.3)
57
+ rake (~> 10.0)
58
+ rspec (~> 2.14)
data/LICENCE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2014 - Bernard Lambeau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ CHANGELOG.md
2
+ Gemfile
3
+ Gemfile.lock
4
+ lib/**/*
5
+ LICENCE.md
6
+ Manifest.txt
7
+ qrb.gemspec
8
+ Rakefile
9
+ README.md
10
+ spec/**/*
11
+ tasks/**/*
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ [![Build Status](https://travis-ci.org/blambeau/qrb.png)](https://travis-ci.org/blambeau/qrb)
2
+ [![Dependency Status](https://gemnasium.com/blambeau/qrb.png)](https://gemnasium.com/blambeau/qrb)
3
+ [![Code Climate](https://codeclimate.com/github/blambeau/qrb.png)](https://codeclimate.com/github/blambeau/qrb)
4
+ [![Coverage Status](https://coveralls.io/repos/blambeau/qrb/badge.png?branch=master)](https://coveralls.io/r/blambeau/qrb)
5
+
6
+ # Qrb
7
+
8
+ *Q* is a language for capturing information structure. Think "JSON/XML schema"
9
+ but the correct way. For more information about *Q* itself, see [www.q-lang.io](http://www.q-lang.io)
10
+
11
+ Qrb is the ruby binding of *Q*. It allows defining Q schemas and validating
12
+ and coercing data against them in an idiomatic ruby way.
13
+
14
+ ## Example
15
+
16
+ ```ruby
17
+ require 'qrb'
18
+ require 'json'
19
+
20
+ # Let load a Q schema
21
+ schema = Qrb::DEFAULT_SYSTEM.parse <<-Q
22
+ {
23
+ name: String( s | s.strip.size > 0 ),
24
+ at: DateTime
25
+ }
26
+ Q
27
+
28
+ # Let load some JSON document
29
+ data = JSON.parse <<-JSON
30
+ { "name": "Q", "at": "20142-03-01" }
31
+ JSON
32
+
33
+ # And try dressing that data
34
+ puts schema.dress(data)
35
+ ```
36
+
37
+ ## About this Q binding
38
+
39
+ Qrb tries to provide an idiomatic binding for ruby developers. In particular,
40
+ it uses a simple convention-over-configuration protocol for information
41
+ contracts. This protocol is easily described through an example. The following
42
+ ADT definition:
43
+
44
+ ```ruby
45
+ Color = .Color <rgb> {r: Byte, g: Byte, b: Byte}
46
+ ```
47
+
48
+ expects the following ruby class:
49
+
50
+ ```ruby
51
+ class Color
52
+
53
+ # Constructor & internal representation
54
+ def initialize(r, g, b)
55
+ @r, @g, @b = r, g, b
56
+ end
57
+ attr_reader :r, :g, :b
58
+
59
+ # Public dresser for the RGB information contract on the class
60
+ def self.rgb(tuple)
61
+ new(tuple[:r], tuple[:g], tuple[:b])
62
+ end
63
+
64
+ # Public undresser on the instance
65
+ def to_rgb
66
+ { r: @r, g: @g, b: @b }
67
+ end
68
+
69
+ # ...
70
+
71
+ end
72
+ ```
73
+
74
+ ## About representations
75
+
76
+ The `Rep` representation function mapping Q types to ruby classes is as
77
+ follows:
78
+
79
+ ```ruby
80
+ # Builtins are represented by the corresponding ruby class
81
+ Rep(.Builtin) = Builtin
82
+
83
+ # Sub types are represented by the same representation as the super type
84
+ Rep(SuperType( s | ... )) = Rep(SuperType)
85
+
86
+ # Unions are represented by the corresponding classes. The guaranteed result
87
+ # class is thus the least common super class (^) of the corresponding
88
+ # representations of candidate types
89
+ Rep(T1 | ... | Tn) = Rep(T1) ^ ... ^ Rep(Tn)
90
+
91
+ # Sequences are represented through ::Array.
92
+ Rep([ElmType]) = Array<Rep(ElmType)>
93
+
94
+ # Sets are represented through ::Set.
95
+ Rep({ElmType}) = Set<Rep(ElmType)>
96
+
97
+ # Tuples are represented through ruby ::Hash. Attribute names are always
98
+ # symbolized
99
+ Rep({Ai => Ti}) = Hash<Symbol => Rep(Ti)>
100
+
101
+ # Relations are represented through ruby ::Set of ::Hash.
102
+ Rep({{Ai => Ti}}) = Set<Hash<Symbol => Rep(Ti)>>
103
+
104
+ # Abstract data types are represented through the corresponding class when
105
+ # specified. ADTs behave as Union types if no class is bound.
106
+ Rep(.Builtin <rep> ...) = Builtin
107
+ ```
108
+
109
+ ## About the default system
110
+
111
+ See `lib/qrb/Q/default.q` for the precise definition of the default system.
112
+ In summary,
113
+
114
+ * Most ruby native (data) classes are already aliased to avoid explicit use of
115
+ builtins. In particular, `Integer`, `String`, etc.
116
+ * A `Boolean` union type also hides the TrueClass and FalseClass distinction.
117
+ * Date, Time and DateTime ADTs are also provided that perform common
118
+ conversions from JSON strings, through iso8601.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # We run tests by default
2
+ task :default => :test
3
+
4
+ #
5
+ # Install all tasks found in tasks folder
6
+ #
7
+ # See .rake files there for complete documentation.
8
+ #
9
+ Dir["tasks/*.rake"].each do |taskfile|
10
+ load taskfile
11
+ end
@@ -0,0 +1,29 @@
1
+ # Nil
2
+ NilClass = .NilClass
3
+ Nil = .NilClass
4
+
5
+ # Booleans
6
+ TrueClass = .TrueClass
7
+ FalseClass = .TrueClass
8
+ True = .TrueClass
9
+ False = .FalseClass
10
+ Boolean = .TrueClass|.FalseClass
11
+
12
+ # Numbers
13
+ Numeric = .Numeric
14
+ Fixnum = .Fixnum
15
+ Bignum = .Bignum
16
+ Integer = .Integer
17
+ Float = .Float
18
+ Real = .Float
19
+
20
+ # String
21
+ String = .String
22
+
23
+ # Date, Time, DateTime
24
+ Date = .Date <iso8601> .String \( s | Date.iso8601(s) )
25
+ \( d | d.iso8601 )
26
+ Time = .Time <iso8601> .String \( s | Time.iso8601(s) )
27
+ \( t | t.iso8601 )
28
+ DateTime = .DateTime <iso8601> .String \( s | DateTime.iso8601(s) )
29
+ \( t | t.iso8601 )
@@ -0,0 +1,23 @@
1
+ module Qrb
2
+ module DataType
3
+
4
+ def dress(value, handler = DressHelper.new)
5
+ ad_type.dress(value, handler)
6
+ end
7
+
8
+ def contract(name, infotype)
9
+ ad_contracts[name] = [ Qrb.type(infotype) , method(name) ]
10
+ end
11
+
12
+ private
13
+
14
+ def ad_type
15
+ @ad_type ||= Qrb.adt(self, ad_contracts)
16
+ end
17
+
18
+ def ad_contracts
19
+ @ad_contracts ||= {}
20
+ end
21
+
22
+ end # module DataType
23
+ end # module Qrb
data/lib/qrb/errors.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Qrb
2
+
3
+ class Error < StandardError
4
+
5
+ def initialize(msg, cause = nil)
6
+ super(msg)
7
+ @cause = cause
8
+ end
9
+ attr_reader :cause
10
+
11
+ end # class Error
12
+
13
+ class TypeError < Error
14
+
15
+ def initialize(msg, cause = nil, location = nil)
16
+ super(msg, cause)
17
+ @location = location || ''
18
+ end
19
+ attr_reader :location
20
+
21
+ end # class TypeError
22
+
23
+ end # module Qrb
@@ -0,0 +1,53 @@
1
+ module Qrb
2
+ #
3
+ # Helper class for tuple and relation attributes.
4
+ #
5
+ # An attribute is simply a `(name: AttrName, type: Type)` pair, where the
6
+ # type is a Q type.
7
+ #
8
+ class Attribute
9
+
10
+ def initialize(name, type)
11
+ unless name.is_a?(Symbol)
12
+ raise ArgumentError, "Symbol expected for attribute name, got `#{name}`"
13
+ end
14
+
15
+ unless type.is_a?(Type)
16
+ raise ArgumentError, "Type expected for attribute domain, got `#{type}`"
17
+ end
18
+
19
+ @name, @type = name, type
20
+ end
21
+ attr_reader :name, :type
22
+
23
+ # Fetch the attribute on `arg`, which is expected to be a Hash object.
24
+ #
25
+ # This method allows working with ruby hashes having either Symbols or
26
+ # Strings as keys. It ensures that no Symbol is created by the rest of the
27
+ # code, since this would provide a DoS attack vector under MRI.
28
+ #
29
+ def fetch_on(arg, &bl)
30
+ unless arg.respond_to?(:fetch)
31
+ raise ArgumentError, "Object responding to `fetch` expected"
32
+ end
33
+ arg.fetch(name) do
34
+ arg.fetch(name.to_s, &bl)
35
+ end
36
+ end
37
+
38
+ def to_name
39
+ "#{name}: #{type}"
40
+ end
41
+
42
+ def ==(other)
43
+ return nil unless other.is_a?(Attribute)
44
+ name==other.name and type==other.type
45
+ end
46
+ alias :eql? :==
47
+
48
+ def hash
49
+ name.hash ^ type.hash
50
+ end
51
+
52
+ end # class Attribute
53
+ end # module Qrb
@@ -0,0 +1,25 @@
1
+ module Qrb
2
+ module CollectionType
3
+
4
+ def initialize(elm_type, name = nil)
5
+ unless elm_type.is_a?(Type)
6
+ raise ArgumentError, "Qrb::Type expected, got `#{elm_type}`"
7
+ end
8
+
9
+ super(name)
10
+ @elm_type = elm_type
11
+ end
12
+ attr_reader :elm_type
13
+
14
+ def ==(other)
15
+ return false unless other.is_a?(self.class)
16
+ elm_type == other.elm_type
17
+ end
18
+ alias :eql? :==
19
+
20
+ def hash
21
+ self.class.hash ^ self.elm_type.hash
22
+ end
23
+
24
+ end # module CollectionType
25
+ end # module Qrb
@@ -0,0 +1,68 @@
1
+ module Qrb
2
+ class DressHelper
3
+
4
+ def initialize
5
+ @stack = []
6
+ end
7
+
8
+ def iterate(value)
9
+ value.each.each_with_index do |elm, index|
10
+ deeper(index) do
11
+ yield(elm, index)
12
+ end
13
+ end
14
+ end
15
+
16
+ def deeper(location)
17
+ @stack.push(location.to_s)
18
+ res = yield
19
+ ensure
20
+ @stack.pop
21
+ res
22
+ end
23
+
24
+ def just_try(rescue_on = TypeError)
25
+ [ true, yield ]
26
+ rescue rescue_on => cause
27
+ [ false, nil ]
28
+ end
29
+
30
+ def try(type, value)
31
+ yield
32
+ rescue TypeError => cause
33
+ failed!(type, value, cause)
34
+ end
35
+
36
+ def failed!(type, value, cause = nil)
37
+ msg = default_error_message(type, value)
38
+ raise TypeError.new(msg, cause, location)
39
+ end
40
+
41
+ def fail!(msg, cause = nil)
42
+ raise TypeError.new(msg, cause, location)
43
+ end
44
+
45
+ def default_error_message(type, value)
46
+ value_s, type_s = value_to_s(value), type_to_s(type)
47
+ "Invalid value `#{value_s}` for #{type_s}"
48
+ end
49
+
50
+ def location
51
+ @stack.join('/')
52
+ end
53
+
54
+ private
55
+
56
+ def value_to_s(value)
57
+ return 'nil' if value.nil?
58
+ s = value.to_s
59
+ s = "#{s[0..25]}..." if s.size>25
60
+ s
61
+ end
62
+
63
+ def type_to_s(type)
64
+ type.name.to_s
65
+ end
66
+
67
+ end # class DressHelper
68
+ end # module Qrb
@@ -0,0 +1,57 @@
1
+ module Qrb
2
+ #
3
+ # Helper class for tuple and relation types.
4
+ #
5
+ # A heading is a set of attributes, with the constraint that no two
6
+ # attributes have the same name.
7
+ #
8
+ class Heading
9
+ include Enumerable
10
+
11
+ def initialize(attributes)
12
+ unless attributes.is_a?(Enumerable) and \
13
+ attributes.all?{|a| a.is_a?(Attribute) }
14
+ raise ArgumentError, "Enumerable[Attribute] expected"
15
+ end
16
+
17
+ @attributes = {}
18
+ attributes.each do |attr|
19
+ if @attributes[attr.name]
20
+ raise ArgumentError, "Attribute names must be unique"
21
+ end
22
+ @attributes[attr.name] = attr
23
+ end
24
+ @attributes.freeze
25
+ end
26
+
27
+ def size
28
+ @attributes.size
29
+ end
30
+
31
+ def empty?
32
+ size == 0
33
+ end
34
+
35
+ def each(&bl)
36
+ return to_enum unless bl
37
+ @attributes.values.each(&bl)
38
+ end
39
+
40
+ def to_name
41
+ map(&:to_name).join(', ')
42
+ end
43
+
44
+ def ==(other)
45
+ return nil unless other.is_a?(Heading)
46
+ attributes == other.attributes
47
+ end
48
+
49
+ def hash
50
+ self.class.hash ^ attributes.hash
51
+ end
52
+
53
+ attr_reader :attributes
54
+ protected :attributes
55
+
56
+ end # class Heading
57
+ end # class Qrb
@@ -0,0 +1,178 @@
1
+ module Qrb
2
+ class TypeFactory
3
+
4
+ ################################################################## Factory
5
+
6
+ def type(type, name = nil, &bl)
7
+ return subtype(type(type, name), bl) if bl
8
+ case type
9
+ when Type
10
+ type
11
+ when Module
12
+ BuiltinType.new(type, name || type.name.to_s)
13
+ when Hash
14
+ tuple(type, name)
15
+ when Array
16
+ fail!("Array of arity 1 expected, got `#{type}`") unless type.size==1
17
+ seq(type.first, name)
18
+ when Set
19
+ fail!("Set of arity 1 expected, got `#{type}`") unless type.size==1
20
+ sub = type(type.first)
21
+ sub.is_a?(TupleType) ? relation(sub.heading, name) : set(sub, name)
22
+ when Range
23
+ clazz = [type.begin, type.end].map(&:class).uniq
24
+ fail!("Unsupported range `#{type}`") unless clazz.size==1
25
+ subtype(clazz.first, type)
26
+ when Regexp
27
+ subtype(String, type)
28
+ else
29
+ fail!("Unable to factor a Qrb::Type from `#{type}`")
30
+ end
31
+ end
32
+
33
+ ########################################################### Type Arguments
34
+
35
+ def ruby_type(type)
36
+ unless type.is_a?(Module)
37
+ fail!("Ruby module expected, got `#{type}`")
38
+ end
39
+
40
+ type
41
+ end
42
+
43
+ def name(name)
44
+ unless name.nil? or (name.is_a?(String) and name.strip.size > 1)
45
+ fail!("Wrong type name `#{name}`")
46
+ end
47
+
48
+ name.nil? ? nil : name.strip
49
+ end
50
+
51
+ def constraints(constraints = nil, &bl)
52
+ constrs = {}
53
+ constrs[:predicate] = bl if bl
54
+ constrs[:predicate] = constraints unless constraints.is_a?(Hash)
55
+ constrs.merge!(constraints) if constraints.is_a?(Hash)
56
+ constrs
57
+ end
58
+
59
+ def attribute(name, type)
60
+ Attribute.new(name, type(type))
61
+ end
62
+
63
+ def attributes(attributes)
64
+ case attributes
65
+ when Hash
66
+ attributes.each_pair.map do |name, type|
67
+ attribute(name, type)
68
+ end
69
+ else
70
+ fail!("Hash expected, got `#{attributes}`")
71
+ end
72
+ end
73
+
74
+ def heading(heading)
75
+ case heading
76
+ when Heading
77
+ heading
78
+ when TupleType, RelationType
79
+ heading.heading
80
+ when Hash
81
+ Heading.new(attributes(heading))
82
+ else
83
+ fail!("Heading expected, got `#{heading}`")
84
+ end
85
+ end
86
+
87
+ def contracts(contracts)
88
+ unless contracts.is_a?(Hash)
89
+ fail!("Hash expected, got `#{contracts}`")
90
+ end
91
+ unless (invalid = contracts.keys.reject{|k| k.is_a?(Symbol) }).empty?
92
+ fail!("Invalid contract names `#{invalid}`")
93
+ end
94
+
95
+ contracts
96
+ end
97
+
98
+ ########################################################## Type generators
99
+
100
+ def builtin(ruby_type, name = nil)
101
+ ruby_type = ruby_type(ruby_type)
102
+ name = name(name)
103
+
104
+ BuiltinType.new(ruby_type, name)
105
+ end
106
+
107
+ def adt(ruby_type, contracts, name = nil)
108
+ ruby_type = ruby_type(ruby_type) if ruby_type
109
+ contracts = contracts(contracts)
110
+ name = name(name)
111
+
112
+ AdType.new(ruby_type, contracts, name)
113
+ end
114
+
115
+ ### Sub and union
116
+
117
+ def subtype(super_type, constraints = nil, name = nil, &bl)
118
+ super_type = type(super_type)
119
+ constraints = constraints(constraints, &bl)
120
+ name = name(name)
121
+
122
+ SubType.new(super_type, constraints, name)
123
+ end
124
+
125
+ def union(*args)
126
+ candidates, name = [], nil
127
+ args.each do |arg|
128
+ case arg
129
+ when Array then candidates = arg.map{|t| type(t) }
130
+ when String then name = name(arg)
131
+ else
132
+ candidates << type(arg)
133
+ end
134
+ end
135
+
136
+ UnionType.new(candidates, name)
137
+ end
138
+
139
+ ### Collections
140
+
141
+ def seq(elm_type, name = nil)
142
+ elm_type = type(elm_type)
143
+ name = name(name)
144
+
145
+ SeqType.new(elm_type, name)
146
+ end
147
+
148
+ def set(elm_type, name = nil)
149
+ elm_type = type(elm_type)
150
+ name = name(name)
151
+
152
+ SetType.new(elm_type, name)
153
+ end
154
+
155
+ ### Tuples and relations
156
+
157
+ def tuple(heading, name = nil)
158
+ heading = heading(heading)
159
+ name = name(name)
160
+
161
+ TupleType.new(heading, name)
162
+ end
163
+
164
+ def relation(heading, name = nil)
165
+ heading = heading(heading)
166
+ name = name(name)
167
+
168
+ RelationType.new(heading, name)
169
+ end
170
+
171
+ private
172
+
173
+ def fail!(message)
174
+ raise ArgumentError, message, caller
175
+ end
176
+
177
+ end # class TypeBuilder
178
+ end # module Qrb