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