qrb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +58 -0
- data/LICENCE.md +22 -0
- data/Manifest.txt +11 -0
- data/README.md +118 -0
- data/Rakefile +11 -0
- data/lib/qrb/Q/default.q +29 -0
- data/lib/qrb/data_type.rb +23 -0
- data/lib/qrb/errors.rb +23 -0
- data/lib/qrb/support/attribute.rb +53 -0
- data/lib/qrb/support/collection_type.rb +25 -0
- data/lib/qrb/support/dress_helper.rb +68 -0
- data/lib/qrb/support/heading.rb +57 -0
- data/lib/qrb/support/type_factory.rb +178 -0
- data/lib/qrb/support.rb +5 -0
- data/lib/qrb/syntax/ad_type.rb +25 -0
- data/lib/qrb/syntax/attribute.rb +11 -0
- data/lib/qrb/syntax/builtin_type.rb +13 -0
- data/lib/qrb/syntax/constraint_def.rb +11 -0
- data/lib/qrb/syntax/constraints.rb +18 -0
- data/lib/qrb/syntax/contract.rb +25 -0
- data/lib/qrb/syntax/definitions.rb +14 -0
- data/lib/qrb/syntax/expression.rb +12 -0
- data/lib/qrb/syntax/heading.rb +15 -0
- data/lib/qrb/syntax/lambda_expr.rb +11 -0
- data/lib/qrb/syntax/named_constraint.rb +11 -0
- data/lib/qrb/syntax/q.citrus +195 -0
- data/lib/qrb/syntax/relation_type.rb +11 -0
- data/lib/qrb/syntax/seq_type.rb +12 -0
- data/lib/qrb/syntax/set_type.rb +12 -0
- data/lib/qrb/syntax/sub_type.rb +13 -0
- data/lib/qrb/syntax/support.rb +13 -0
- data/lib/qrb/syntax/system.rb +15 -0
- data/lib/qrb/syntax/tuple_type.rb +11 -0
- data/lib/qrb/syntax/type_def.rb +14 -0
- data/lib/qrb/syntax/type_ref.rb +13 -0
- data/lib/qrb/syntax/union_type.rb +12 -0
- data/lib/qrb/syntax/unnamed_constraint.rb +11 -0
- data/lib/qrb/syntax.rb +42 -0
- data/lib/qrb/system.rb +63 -0
- data/lib/qrb/type/ad_type.rb +111 -0
- data/lib/qrb/type/builtin_type.rb +56 -0
- data/lib/qrb/type/relation_type.rb +81 -0
- data/lib/qrb/type/seq_type.rb +51 -0
- data/lib/qrb/type/set_type.rb +52 -0
- data/lib/qrb/type/sub_type.rb +94 -0
- data/lib/qrb/type/tuple_type.rb +99 -0
- data/lib/qrb/type/union_type.rb +78 -0
- data/lib/qrb/type.rb +63 -0
- data/lib/qrb/version.rb +14 -0
- data/lib/qrb.rb +63 -0
- data/qrb.gemspec +186 -0
- data/spec/acceptance/Q/test_default.rb +96 -0
- data/spec/acceptance/Q/test_parsing.rb +15 -0
- data/spec/acceptance/ad_type/test_in_q.rb +82 -0
- data/spec/acceptance/ad_type/test_in_ruby.rb +60 -0
- data/spec/spec_helper.rb +68 -0
- data/spec/unit/attribute/test_equality.rb +26 -0
- data/spec/unit/attribute/test_fetch_on.rb +50 -0
- data/spec/unit/attribute/test_initialize.rb +13 -0
- data/spec/unit/attribute/test_to_name.rb +10 -0
- data/spec/unit/heading/test_each.rb +28 -0
- data/spec/unit/heading/test_equality.rb +28 -0
- data/spec/unit/heading/test_initialize.rb +36 -0
- data/spec/unit/heading/test_size.rb +30 -0
- data/spec/unit/heading/test_to_name.rb +32 -0
- data/spec/unit/qrb/test_parse.rb +18 -0
- data/spec/unit/syntax/nodes/test_ad_type.rb +94 -0
- data/spec/unit/syntax/nodes/test_attribute.rb +25 -0
- data/spec/unit/syntax/nodes/test_builtin_type.rb +32 -0
- data/spec/unit/syntax/nodes/test_comment.rb +26 -0
- data/spec/unit/syntax/nodes/test_constraint_def.rb +27 -0
- data/spec/unit/syntax/nodes/test_constraints.rb +51 -0
- data/spec/unit/syntax/nodes/test_contract.rb +62 -0
- data/spec/unit/syntax/nodes/test_expression.rb +43 -0
- data/spec/unit/syntax/nodes/test_heading.rb +41 -0
- data/spec/unit/syntax/nodes/test_named_constraint.rb +31 -0
- data/spec/unit/syntax/nodes/test_relation_type.rb +41 -0
- data/spec/unit/syntax/nodes/test_seq_type.rb +24 -0
- data/spec/unit/syntax/nodes/test_set_type.rb +24 -0
- data/spec/unit/syntax/nodes/test_spacing.rb +25 -0
- data/spec/unit/syntax/nodes/test_sub_type.rb +52 -0
- data/spec/unit/syntax/nodes/test_tuple_type.rb +41 -0
- data/spec/unit/syntax/nodes/test_union_type.rb +23 -0
- data/spec/unit/syntax/nodes/test_unnamed_constraint.rb +31 -0
- data/spec/unit/syntax/test_compile_type.rb +22 -0
- data/spec/unit/system/test_add_type.rb +47 -0
- data/spec/unit/system/test_dsl.rb +30 -0
- data/spec/unit/system/test_dup.rb +30 -0
- data/spec/unit/system/test_fetch.rb +42 -0
- data/spec/unit/system/test_get_type.rb +30 -0
- data/spec/unit/system/test_initialize.rb +10 -0
- data/spec/unit/test_qrb.rb +15 -0
- data/spec/unit/type/ad_type/test_default_name.rb +15 -0
- data/spec/unit/type/ad_type/test_dress.rb +55 -0
- data/spec/unit/type/ad_type/test_include.rb +22 -0
- data/spec/unit/type/ad_type/test_initialize.rb +40 -0
- data/spec/unit/type/ad_type/test_name.rb +20 -0
- data/spec/unit/type/builtin_type/test_default_name.rb +12 -0
- data/spec/unit/type/builtin_type/test_dress.rb +33 -0
- data/spec/unit/type/builtin_type/test_equality.rb +26 -0
- data/spec/unit/type/builtin_type/test_include.rb +22 -0
- data/spec/unit/type/builtin_type/test_initialize.rb +12 -0
- data/spec/unit/type/builtin_type/test_name.rb +24 -0
- data/spec/unit/type/relation_type/test_default_name.rb +16 -0
- data/spec/unit/type/relation_type/test_dress.rb +164 -0
- data/spec/unit/type/relation_type/test_equality.rb +32 -0
- data/spec/unit/type/relation_type/test_include.rb +46 -0
- data/spec/unit/type/relation_type/test_initialize.rb +26 -0
- data/spec/unit/type/relation_type/test_name.rb +24 -0
- data/spec/unit/type/seq_type/test_default_name.rb +14 -0
- data/spec/unit/type/seq_type/test_dress.rb +49 -0
- data/spec/unit/type/seq_type/test_equality.rb +26 -0
- data/spec/unit/type/seq_type/test_include.rb +43 -0
- data/spec/unit/type/seq_type/test_initialize.rb +28 -0
- data/spec/unit/type/seq_type/test_name.rb +24 -0
- data/spec/unit/type/set_type/test_default_name.rb +14 -0
- data/spec/unit/type/set_type/test_dress.rb +66 -0
- data/spec/unit/type/set_type/test_equality.rb +26 -0
- data/spec/unit/type/set_type/test_include.rb +43 -0
- data/spec/unit/type/set_type/test_initialize.rb +28 -0
- data/spec/unit/type/set_type/test_name.rb +24 -0
- data/spec/unit/type/sub_type/test_default_name.rb +14 -0
- data/spec/unit/type/sub_type/test_dress.rb +75 -0
- data/spec/unit/type/sub_type/test_equality.rb +34 -0
- data/spec/unit/type/sub_type/test_include.rb +34 -0
- data/spec/unit/type/sub_type/test_initialize.rb +16 -0
- data/spec/unit/type/sub_type/test_name.rb +24 -0
- data/spec/unit/type/tuple_type/test_default_name.rb +14 -0
- data/spec/unit/type/tuple_type/test_dress.rb +112 -0
- data/spec/unit/type/tuple_type/test_equality.rb +32 -0
- data/spec/unit/type/tuple_type/test_include.rb +38 -0
- data/spec/unit/type/tuple_type/test_initialize.rb +30 -0
- data/spec/unit/type/tuple_type/test_name.rb +24 -0
- data/spec/unit/type/union_type/test_default_name.rb +12 -0
- data/spec/unit/type/union_type/test_dress.rb +43 -0
- data/spec/unit/type/union_type/test_equality.rb +30 -0
- data/spec/unit/type/union_type/test_include.rb +28 -0
- data/spec/unit/type/union_type/test_initialize.rb +24 -0
- data/spec/unit/type/union_type/test_name.rb +20 -0
- data/spec/unit/type_factory/dsl/test_adt.rb +54 -0
- data/spec/unit/type_factory/dsl/test_attribute.rb +37 -0
- data/spec/unit/type_factory/dsl/test_attributes.rb +41 -0
- data/spec/unit/type_factory/dsl/test_builtin.rb +45 -0
- data/spec/unit/type_factory/dsl/test_relation.rb +85 -0
- data/spec/unit/type_factory/dsl/test_seq.rb +57 -0
- data/spec/unit/type_factory/dsl/test_set.rb +57 -0
- data/spec/unit/type_factory/dsl/test_subtype.rb +91 -0
- data/spec/unit/type_factory/dsl/test_tuple.rb +73 -0
- data/spec/unit/type_factory/dsl/test_union.rb +81 -0
- data/spec/unit/type_factory/factory/test_builtin.rb +24 -0
- data/spec/unit/type_factory/factory/test_seq_type.rb +44 -0
- data/spec/unit/type_factory/factory/test_set_type.rb +44 -0
- data/spec/unit/type_factory/factory/test_sub_type.rb +53 -0
- data/spec/unit/type_factory/factory/test_tuple_type.rb +43 -0
- data/tasks/gem.rake +73 -0
- data/tasks/test.rake +31 -0
- metadata +344 -0
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
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
data/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
[](https://travis-ci.org/blambeau/qrb)
|
|
2
|
+
[](https://gemnasium.com/blambeau/qrb)
|
|
3
|
+
[](https://codeclimate.com/github/blambeau/qrb)
|
|
4
|
+
[](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
data/lib/qrb/Q/default.q
ADDED
|
@@ -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
|