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
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