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
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'set'
|
2
|
+
module Qrb
|
3
|
+
#
|
4
|
+
# The Abtract/Algebraic Data Type is a generator to build user-defined
|
5
|
+
# information types in a more abstract way than using sub types. For
|
6
|
+
# instance, a Color could be defined as follows:
|
7
|
+
#
|
8
|
+
# Color := <Rgb> {r: Byte, g: Byte, b: Byte},
|
9
|
+
# <Hex> String( s | s =~ /^#[0-9a-f]{6}$/i )
|
10
|
+
#
|
11
|
+
# Such a declaration does not define a new type by *subtyping* but instead
|
12
|
+
# declares so-called possible representations for the color. Here, two
|
13
|
+
# possible representations are defined: one is a rgb triple through a Tuple
|
14
|
+
# type, the other is an hexadecimal notation through a subset of String.
|
15
|
+
#
|
16
|
+
# Given an AdType, Q requires special dress/undress function pairs to
|
17
|
+
# encode/decode from the type to the concrete representations and vice versa.
|
18
|
+
# This pair of functions is called the "information contract".
|
19
|
+
#
|
20
|
+
# This class allows capturing such information types. For instance,
|
21
|
+
#
|
22
|
+
# # This is the concrete representation of Q's Color, through a usual
|
23
|
+
# # Ruby ADT (we call it ColorImpl here to avoid confusion, but in
|
24
|
+
# # practice one would simply call it Color).
|
25
|
+
# class ColorImpl
|
26
|
+
# # ... your usual color implementation
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # The RGB info type: {r: Byte, g: Byte, b: Byte}
|
30
|
+
# rgb_infotype = TupleType.new(...)
|
31
|
+
#
|
32
|
+
# # The RGB contract converter, an object that responds to `call` to
|
33
|
+
# # convert from a valid Hash[r: Fixnum, ...] to a ColorImpl instance.
|
34
|
+
# rgb_contract = ...
|
35
|
+
#
|
36
|
+
# AdType.new(ColorImpl, rgb: [rgb_infotype, rgb_contract], hex: ...)
|
37
|
+
#
|
38
|
+
# The ruby ADT class is of course used as concrete representation of the
|
39
|
+
# AdType, that is,
|
40
|
+
#
|
41
|
+
# R(Color) = ColorImpl
|
42
|
+
#
|
43
|
+
# Accordingly, the `dress` transformation function has the following
|
44
|
+
# signature:
|
45
|
+
#
|
46
|
+
# dress :: Alpha -> Color throws TypeError
|
47
|
+
# dress :: Object -> ColorImpl throws TypeError
|
48
|
+
#
|
49
|
+
class AdType < Type
|
50
|
+
|
51
|
+
def initialize(ruby_type, contracts, name = nil)
|
52
|
+
unless ruby_type.nil? or ruby_type.is_a?(Module)
|
53
|
+
raise ArgumentError, "Module expected, got `#{ruby_type}`"
|
54
|
+
end
|
55
|
+
unless contracts.is_a?(Hash)
|
56
|
+
raise ArgumentError, "Hash expected, got `#{contracts}`"
|
57
|
+
end
|
58
|
+
invalid = contracts.values.reject{|v|
|
59
|
+
v.is_a?(Array) and v.size == 2 and v.first.is_a?(Type) and v.last.respond_to?(:call)
|
60
|
+
}
|
61
|
+
unless invalid.empty?
|
62
|
+
raise ArgumentError, "Invalid contracts `#{invalid}`"
|
63
|
+
end
|
64
|
+
|
65
|
+
super(name)
|
66
|
+
@ruby_type = ruby_type
|
67
|
+
@contracts = contracts.freeze
|
68
|
+
end
|
69
|
+
attr_reader :ruby_type, :contracts
|
70
|
+
|
71
|
+
def contract_names
|
72
|
+
contracts.keys
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_name
|
76
|
+
(ruby_type && ruby_type.name.to_s) || "Anonymous"
|
77
|
+
end
|
78
|
+
|
79
|
+
def include?(value)
|
80
|
+
ruby_type === value
|
81
|
+
end
|
82
|
+
|
83
|
+
def dress(value, handler = DressHelper.new)
|
84
|
+
# Up should be idempotent with respect to the ADT
|
85
|
+
return value if ruby_type and value.is_a?(ruby_type)
|
86
|
+
|
87
|
+
# Try each contract in turn. Do nothing on TypeError as
|
88
|
+
# the next candidate could be the good one! Return the
|
89
|
+
# first successfully uped.
|
90
|
+
contracts.each_pair do |name, (infotype,upper)|
|
91
|
+
|
92
|
+
# First make the dress transformation on the information type
|
93
|
+
success, uped = handler.just_try do
|
94
|
+
infotype.dress(value, handler)
|
95
|
+
end
|
96
|
+
next unless success
|
97
|
+
|
98
|
+
# Seems nice, just try to get one stage higher now
|
99
|
+
success, uped = handler.just_try(StandardError) do
|
100
|
+
upper.call(uped)
|
101
|
+
end
|
102
|
+
return uped if success
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# No one succeeded, just fail
|
107
|
+
handler.failed!(self, value)
|
108
|
+
end
|
109
|
+
|
110
|
+
end # class AdType
|
111
|
+
end # module Qrb
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# A BuiltinType generator allows capuring an information type to a type of
|
4
|
+
# the host language, here a Ruby class. For instance,
|
5
|
+
#
|
6
|
+
# Int := BuiltinType(ruby.Fixnum)
|
7
|
+
#
|
8
|
+
# The set of values captured by the information type is the same set of
|
9
|
+
# values that can be represented by the host type. In the example, `Int`
|
10
|
+
# captures the same set of numbers as ruby's Fixnum.
|
11
|
+
#
|
12
|
+
# The ruby class is used as concrete representation of the information type.
|
13
|
+
# In the example:
|
14
|
+
#
|
15
|
+
# R(Int) = Fixnum
|
16
|
+
#
|
17
|
+
# Accordingly, the `dress` transformation function has the following signature:
|
18
|
+
#
|
19
|
+
# dress :: Alpha -> Int throws TypeError
|
20
|
+
# dress :: Object -> Fixnum throws TypeError
|
21
|
+
#
|
22
|
+
class BuiltinType < Type
|
23
|
+
|
24
|
+
def initialize(ruby_type, name = nil)
|
25
|
+
super(name)
|
26
|
+
@ruby_type = ruby_type
|
27
|
+
end
|
28
|
+
attr_reader :ruby_type
|
29
|
+
|
30
|
+
def default_name
|
31
|
+
@ruby_type.name.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def include?(value)
|
35
|
+
ruby_type === value
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check that `value` is a valid instance of `ruby_type` through `===` or
|
39
|
+
# raise an error.
|
40
|
+
def dress(value, handler = DressHelper.new)
|
41
|
+
handler.failed!(self, value) unless ruby_type===value
|
42
|
+
value
|
43
|
+
end
|
44
|
+
|
45
|
+
def ==(other)
|
46
|
+
return false unless other.is_a?(BuiltinType)
|
47
|
+
other.ruby_type==ruby_type
|
48
|
+
end
|
49
|
+
alias :eql? :==
|
50
|
+
|
51
|
+
def hash
|
52
|
+
self.class.hash ^ ruby_type.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
end # class BuiltinType
|
56
|
+
end # module Qrb
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# The Relation type generator allows capturing sets of information facts,
|
4
|
+
# i.e. sets of tuples (of same heading). E.g.
|
5
|
+
#
|
6
|
+
# ColoredPoints = {{ point: Point, color: Color }}
|
7
|
+
#
|
8
|
+
# This class allows capturing relation types, in a way similar to TupleType:
|
9
|
+
#
|
10
|
+
# ColoredPoints = RelationType.new( Heading[...] )
|
11
|
+
#
|
12
|
+
# A ruby Set is used as concrete representation, and will contain hashes
|
13
|
+
# that are valid representations of the associated tuple type:
|
14
|
+
#
|
15
|
+
# R(ColoredPoints) = Set[ R({...}) ] = Set[Hash[...]]
|
16
|
+
#
|
17
|
+
# Accordingly, the dress transformation function has the signature below.
|
18
|
+
# It expects an Enumerable as input and fails if any duplicate is found
|
19
|
+
# (after tuple transformation), or if any tuple fails at being transformed.
|
20
|
+
#
|
21
|
+
# dress :: Alpha -> ColoredPoints throws TypeError
|
22
|
+
# dress :: Object -> Set[Hash[...]] throws TypeError
|
23
|
+
#
|
24
|
+
class RelationType < Type
|
25
|
+
|
26
|
+
def initialize(heading, name = nil)
|
27
|
+
unless heading.is_a?(Heading)
|
28
|
+
raise ArgumentError, "Heading expected, got `#{heading}`"
|
29
|
+
end
|
30
|
+
|
31
|
+
super(name)
|
32
|
+
@heading = heading
|
33
|
+
end
|
34
|
+
attr_reader :heading
|
35
|
+
|
36
|
+
def default_name
|
37
|
+
"{{#{heading.to_name}}}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def include?(value)
|
41
|
+
value.is_a?(Set) && value.all?{|tuple|
|
42
|
+
tuple_type.include?(tuple)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Apply the corresponding TupleType's `dress` to every element of `value`
|
47
|
+
# (any enumerable). Return a Set of transformed tuples. Fail if anything
|
48
|
+
# goes wrong transforming tuples or if duplicates are found.
|
49
|
+
def dress(value, handler = DressHelper.new)
|
50
|
+
handler.failed!(self, value) unless value.respond_to?(:each)
|
51
|
+
|
52
|
+
# Up every tuple and keep results in a Set
|
53
|
+
set = Set.new
|
54
|
+
handler.iterate(value) do |tuple, index|
|
55
|
+
tuple = tuple_type.dress(tuple, handler)
|
56
|
+
handler.fail!("Duplicate tuple") if set.include?(tuple)
|
57
|
+
set << tuple
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return built tuples
|
61
|
+
set
|
62
|
+
end
|
63
|
+
|
64
|
+
def ==(other)
|
65
|
+
return false unless other.is_a?(RelationType)
|
66
|
+
heading == other.heading
|
67
|
+
end
|
68
|
+
alias :eql? :==
|
69
|
+
|
70
|
+
def hash
|
71
|
+
self.class.hash ^ heading.hash
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def tuple_type
|
77
|
+
@tuple_type ||= TupleType.new(heading)
|
78
|
+
end
|
79
|
+
|
80
|
+
end # class RelationType
|
81
|
+
end # module Qrb
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# The Seq type generator allows capturing sequences of values. For example,
|
4
|
+
# a (implicitely temporal) series of temperature measures could be written
|
5
|
+
# as:
|
6
|
+
#
|
7
|
+
# Measures = [Temperature]
|
8
|
+
#
|
9
|
+
# This class allows capturing those sequence types, e.g.:
|
10
|
+
#
|
11
|
+
# Temperature = BuiltinType.new(Float)
|
12
|
+
# Measures = SeqType.new(Temperature)
|
13
|
+
#
|
14
|
+
# An array of values is used as concrete representation for such sequences:
|
15
|
+
#
|
16
|
+
# R(Measures) = Array[R(Temperature)] = Array[Float]
|
17
|
+
#
|
18
|
+
# Accordingly, the `dress` transformation function has the signature below.
|
19
|
+
# It expects it's Alpha/Object argument to be a object responding to
|
20
|
+
# `each` (with the ruby idiomatic semantics that such a `each` returns
|
21
|
+
# an Enumerator when invoked without block).
|
22
|
+
#
|
23
|
+
# dress :: Alpha -> Measures throws TypeError
|
24
|
+
# dress :: Object -> Array[Float] throws TypeError
|
25
|
+
#
|
26
|
+
class SeqType < Type
|
27
|
+
include CollectionType
|
28
|
+
|
29
|
+
def include?(value)
|
30
|
+
value.is_a?(::Array) and value.all?{|v| elm_type.include?(v) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Apply the element type's `dress` transformation to each element of
|
34
|
+
# `value` (expected to respond to `each`). Return converted values in a
|
35
|
+
# ruby Array.
|
36
|
+
def dress(value, handler = DressHelper.new)
|
37
|
+
handler.failed!(self, value) unless value.respond_to?(:each)
|
38
|
+
|
39
|
+
array = []
|
40
|
+
handler.iterate(value) do |elm, index|
|
41
|
+
array << elm_type.dress(elm, handler)
|
42
|
+
end
|
43
|
+
array
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_name
|
47
|
+
"[#{elm_type.name}]"
|
48
|
+
end
|
49
|
+
|
50
|
+
end # class SeqType
|
51
|
+
end # module Qrb
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# The Set type generator allows capturing an unordered set of values (i.e.
|
4
|
+
# with no duplicates). For example, a set of emails could be captured with:
|
5
|
+
#
|
6
|
+
# Adresses = {Email}
|
7
|
+
#
|
8
|
+
# This class allows capturing those set types, e.g.:
|
9
|
+
#
|
10
|
+
# Email = BuiltinType.new(String)
|
11
|
+
# Adresses = SetType.new(Email)
|
12
|
+
#
|
13
|
+
# A ruby Set of values is used as concrete representation for such sets:
|
14
|
+
#
|
15
|
+
# R(Adresses) = Set[R(Email)] = Set[String]
|
16
|
+
#
|
17
|
+
# Accordingly, the `dress` transformation function has the signature below.
|
18
|
+
# It expects it's Alpha/Object argument to be a object responding to
|
19
|
+
# `each` (with the ruby idiomatic semantics that such a `each` returns
|
20
|
+
# an Enumerator when invoked without block).
|
21
|
+
#
|
22
|
+
# dress :: Alpha -> Adresses throws TypeError
|
23
|
+
# dress :: Object -> Set[String] throws TypeError
|
24
|
+
#
|
25
|
+
class SetType < Type
|
26
|
+
include CollectionType
|
27
|
+
|
28
|
+
def default_name
|
29
|
+
"{#{elm_type.name}}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def include?(value)
|
33
|
+
value.is_a?(::Set) and value.all?{|v| elm_type.include?(v) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Apply the element type's `dress` transformation to each element of
|
37
|
+
# `value` (expected to respond to `each`). Return converted values in a
|
38
|
+
# ruby Set.
|
39
|
+
def dress(value, handler = DressHelper.new)
|
40
|
+
handler.failed!(self, value) unless value.respond_to?(:each)
|
41
|
+
|
42
|
+
set = Set.new
|
43
|
+
handler.iterate(value) do |elm, index|
|
44
|
+
elm = elm_type.dress(elm, handler)
|
45
|
+
handler.fail!("Duplicate value `#{elm}`") if set.include?(elm)
|
46
|
+
set << elm
|
47
|
+
end
|
48
|
+
set
|
49
|
+
end
|
50
|
+
|
51
|
+
end # class SetType
|
52
|
+
end # module Qrb
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# A sub type generator, through specialization by constraints.
|
4
|
+
#
|
5
|
+
# A sub type captures a subset of the values of a super type, through a
|
6
|
+
# constraint. For instance, a Byte type can be defined as a subset of all
|
7
|
+
# integers, as follows:
|
8
|
+
#
|
9
|
+
# Byte := Integer( i | i >= 0 and i <= 255 )
|
10
|
+
#
|
11
|
+
# This class allows defining such sub types with multiple named constraints.
|
12
|
+
# For instance,
|
13
|
+
#
|
14
|
+
# Int = BuiltinType.new(Integer)
|
15
|
+
# Byte = SubType.new(Int, positive: ->(i){ i >= 0 },
|
16
|
+
# small: ->(i){ i <= 255 })
|
17
|
+
#
|
18
|
+
# The concrete representation of the super type is kept as representation
|
19
|
+
# of the sub type. In other words:
|
20
|
+
#
|
21
|
+
# R(Byte) = R(Int) = Fixnum
|
22
|
+
#
|
23
|
+
# Accordingly, the `dress` transformation function has the following signature:
|
24
|
+
#
|
25
|
+
# dress :: Alpha -> Byte throws TypeError
|
26
|
+
# dress :: Object -> Fixnum throws TypeError
|
27
|
+
#
|
28
|
+
class SubType < Type
|
29
|
+
|
30
|
+
DEFAULT_CONSTRAINT_NAMES = [:default, :predicate].freeze
|
31
|
+
|
32
|
+
def initialize(super_type, constraints, name = nil)
|
33
|
+
unless super_type.is_a?(Type)
|
34
|
+
raise ArgumentError, "Qrb::Type expected, got #{super_type}"
|
35
|
+
end
|
36
|
+
|
37
|
+
unless constraints.is_a?(Hash)
|
38
|
+
raise ArgumentError, "Hash expected for constraints, got #{constraints}"
|
39
|
+
end
|
40
|
+
|
41
|
+
super(name)
|
42
|
+
@super_type, @constraints = super_type, constraints.freeze
|
43
|
+
end
|
44
|
+
attr_reader :super_type, :constraints
|
45
|
+
|
46
|
+
def default_name
|
47
|
+
constraints.keys.first.to_s.capitalize
|
48
|
+
end
|
49
|
+
|
50
|
+
def include?(value)
|
51
|
+
super_type.include?(value) && constraints.all?{|_,c| c===value }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Check that `value` can be uped through the supertype, then verify all
|
55
|
+
# constraints. Raise an error if anything goes wrong.
|
56
|
+
def dress(value, handler = DressHelper.new)
|
57
|
+
# Check that the supertype is able to dress the value.
|
58
|
+
# Rewrite and set cause to any encountered TypeError.
|
59
|
+
uped = handler.try(self, value) do
|
60
|
+
super_type.dress(value, handler)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check each constraint in turn
|
64
|
+
constraints.each_pair do |name, constraint|
|
65
|
+
next if constraint===uped
|
66
|
+
msg = handler.default_error_message(self, value)
|
67
|
+
msg << " (not #{name})" unless default_constraint?(name)
|
68
|
+
handler.fail!(msg)
|
69
|
+
end
|
70
|
+
|
71
|
+
# seems good, return the uped value
|
72
|
+
uped
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
return false unless other.is_a?(SubType)
|
77
|
+
other.super_type == super_type and \
|
78
|
+
set_equal?(constraints.values, other.constraints.values)
|
79
|
+
end
|
80
|
+
alias :eql? :==
|
81
|
+
|
82
|
+
def hash
|
83
|
+
self.class.hash ^ super_type.hash ^ set_hash(constraints.values)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def default_constraint?(name)
|
89
|
+
DEFAULT_CONSTRAINT_NAMES.include?(name) or \
|
90
|
+
name.to_s.capitalize == self.name
|
91
|
+
end
|
92
|
+
|
93
|
+
end # class BuiltinType
|
94
|
+
end # module Qrb
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# The Tuple type generator allows capturing information *facts*. For
|
4
|
+
# instance, a Point type could be defined as follows:
|
5
|
+
#
|
6
|
+
# Point = {r: Length, theta: Angle}
|
7
|
+
#
|
8
|
+
# This class allows capturing those information types, as in:
|
9
|
+
#
|
10
|
+
# Length = BuiltinType.new(Fixnum)
|
11
|
+
# Angle = BuiltinType.new(Float)
|
12
|
+
# Point = TupleType.new(Heading.new([
|
13
|
+
# Attribute.new(:r, Length),
|
14
|
+
# Attribute.new(:theta, Angle)
|
15
|
+
# ]))
|
16
|
+
#
|
17
|
+
# A Hash with Symbol as keys is used as concrete ruby representation for
|
18
|
+
# tuples. The values map to the concrete representations of each attribute
|
19
|
+
# type:
|
20
|
+
#
|
21
|
+
# R(Point) = Hash[r: R(Length), theta: R(Angle)]
|
22
|
+
# = Hash[r: Fixnum, theta: Float]
|
23
|
+
#
|
24
|
+
# Accordingly, the `dress` transformation function has the signature below.
|
25
|
+
# It expects it's Alpha/Object argument to be a Hash with all and only the
|
26
|
+
# expected keys (either as Symbols or Strings). The `dress` function
|
27
|
+
# applies on every attribute value according to their respective type.
|
28
|
+
#
|
29
|
+
# dress :: Alpha -> Point throws TypeError
|
30
|
+
# dress :: Object -> Hash[r: Fixnum, theta: Float] throws TypeError
|
31
|
+
#
|
32
|
+
class TupleType < Type
|
33
|
+
|
34
|
+
def initialize(heading, name = nil)
|
35
|
+
unless heading.is_a?(Heading)
|
36
|
+
raise ArgumentError, "Heading expected, got `#{heading}`"
|
37
|
+
end
|
38
|
+
|
39
|
+
super(name)
|
40
|
+
@heading = heading
|
41
|
+
end
|
42
|
+
attr_reader :heading
|
43
|
+
|
44
|
+
def default_name
|
45
|
+
"{#{heading.to_name}}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def include?(value)
|
49
|
+
return false unless value.is_a?(Hash)
|
50
|
+
return false if value.size > heading.size
|
51
|
+
heading.all? do |attribute|
|
52
|
+
attr_val = value.fetch(attribute.name){
|
53
|
+
return false
|
54
|
+
}
|
55
|
+
attribute.type.include?(attr_val)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Convert `value` (supposed to be Hash) to a Tuple, by checking attributes
|
60
|
+
# and applying `dress` on them in turn. Raise an error if any attribute
|
61
|
+
# is missing or unrecognized, as well as if any sub transformation fails.
|
62
|
+
def dress(value, handler = DressHelper.new)
|
63
|
+
handler.failed!(self, value) unless value.is_a?(Hash)
|
64
|
+
|
65
|
+
# Uped values, i.e. tuple under construction
|
66
|
+
uped = {}
|
67
|
+
|
68
|
+
# Check the tuple arity and fail fast if extra attributes
|
69
|
+
# (missing attributes are handled just after)
|
70
|
+
if value.size > heading.size
|
71
|
+
extra = value.keys.map(&:to_s) - heading.map{|attr| attr.name.to_s }
|
72
|
+
handler.fail!("Unrecognized attribute `#{extra.first}`")
|
73
|
+
end
|
74
|
+
|
75
|
+
# Up each attribute in turn now. Fail on missing ones.
|
76
|
+
heading.each do |attribute|
|
77
|
+
val = attribute.fetch_on(value) do
|
78
|
+
handler.fail!("Missing attribute `#{attribute.name}`")
|
79
|
+
end
|
80
|
+
handler.deeper(attribute.name) do
|
81
|
+
uped[attribute.name] = attribute.type.dress(val, handler)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
uped
|
86
|
+
end
|
87
|
+
|
88
|
+
def ==(other)
|
89
|
+
return false unless other.is_a?(TupleType)
|
90
|
+
heading == other.heading
|
91
|
+
end
|
92
|
+
alias :eql? :==
|
93
|
+
|
94
|
+
def hash
|
95
|
+
self.class.hash ^ heading.hash
|
96
|
+
end
|
97
|
+
|
98
|
+
end # class TupleType
|
99
|
+
end # module Qrb
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'set'
|
2
|
+
module Qrb
|
3
|
+
#
|
4
|
+
# A union type (aka algebraic type) allows capturing information types
|
5
|
+
# through generalization/disjunction. For instance,
|
6
|
+
#
|
7
|
+
# Numeric = Int|Real
|
8
|
+
#
|
9
|
+
# This class allows capturing such union types, as follows:
|
10
|
+
#
|
11
|
+
# Int = BuiltinType.new(Fixnum)
|
12
|
+
# Real = BuiltinType.new(Float)
|
13
|
+
# Numeric = UnionType.new([ Int, Real ])
|
14
|
+
#
|
15
|
+
# When transforming a value through `dress`, the different candidate types
|
16
|
+
# are tried in specified order. The first one that succeeds at building the
|
17
|
+
# value ends the process and the value is simply returned. Accordingly,
|
18
|
+
# the concrete representation will be
|
19
|
+
#
|
20
|
+
# R(Numeric) = R(Int) ^ R(Real) = Fixnum ^ Float = Numeric
|
21
|
+
#
|
22
|
+
# where `^` denotes the `least common super type` operator on ruby classes.
|
23
|
+
#
|
24
|
+
# Accordingly, the `dress` transformation function has the following
|
25
|
+
# signature:
|
26
|
+
#
|
27
|
+
# dress :: Alpha -> Numeric throws TypeError
|
28
|
+
# dress :: Object -> Numeric throws TypeError
|
29
|
+
#
|
30
|
+
class UnionType < Type
|
31
|
+
|
32
|
+
def initialize(candidates, name = nil)
|
33
|
+
unless candidates.all?{|c| c.is_a?(Type) }
|
34
|
+
raise ArgumentError, "[Qrb::Type] expected, got #{candidates}"
|
35
|
+
end
|
36
|
+
|
37
|
+
super(name)
|
38
|
+
@candidates = candidates.freeze
|
39
|
+
end
|
40
|
+
attr_reader :candidates
|
41
|
+
|
42
|
+
def include?(value)
|
43
|
+
candidates.any?{|c| c.include?(value) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Invoke `dress` on each candidate type in turn. Return the value
|
47
|
+
# returned by the first one that does not fail. Fail with an TypeError if no
|
48
|
+
# candidate succeeds at tranforming `value`.
|
49
|
+
def dress(value, handler = DressHelper.new)
|
50
|
+
|
51
|
+
# Do nothing on TypeError as the next candidate could be the good one!
|
52
|
+
candidates.each do |c|
|
53
|
+
success, uped = handler.just_try do
|
54
|
+
c.dress(value, handler)
|
55
|
+
end
|
56
|
+
return uped if success
|
57
|
+
end
|
58
|
+
|
59
|
+
# No one succeed, just fail
|
60
|
+
handler.failed!(self, value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def default_name
|
64
|
+
candidates.map(&:name).join('|')
|
65
|
+
end
|
66
|
+
|
67
|
+
def ==(other)
|
68
|
+
return false unless other.is_a?(UnionType)
|
69
|
+
set_equal?(candidates, other.candidates)
|
70
|
+
end
|
71
|
+
alias :eql? :==
|
72
|
+
|
73
|
+
def hash
|
74
|
+
self.class.hash ^ set_hash(self.candidates)
|
75
|
+
end
|
76
|
+
|
77
|
+
end # class UnionType
|
78
|
+
end # module Qrb
|
data/lib/qrb/type.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Qrb
|
2
|
+
#
|
3
|
+
# Abstract class for Q type (generators).
|
4
|
+
#
|
5
|
+
class Type
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
unless name.nil? or name.is_a?(String)
|
9
|
+
raise ArgumentError, "String expected for type name, got `#{name}`"
|
10
|
+
end
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@name || default_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def name=(n)
|
19
|
+
raise Error, "Name already set to `#{@name}`" unless @name.nil?
|
20
|
+
@name = n
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
name.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
# Check if `value` belongs to this type. Returns true if it's the case,
|
28
|
+
# false otherwise.
|
29
|
+
#
|
30
|
+
# For belonging to the type, `value` MUST be a valid representation, not
|
31
|
+
# an 'approximation' or some 'similar' representation. In particular,
|
32
|
+
# returning true means that no dressing is required for using `value` as a
|
33
|
+
# proper one. Similarly, the method MUST always return true on a value
|
34
|
+
# directly obtained through `dress`.
|
35
|
+
#
|
36
|
+
def include?(value)
|
37
|
+
raise NotImplementedError, "Missing #{self.class.name}#include?"
|
38
|
+
end
|
39
|
+
|
40
|
+
def dress(*args)
|
41
|
+
raise NotImplementedError, "Missing #{self.class.name}#dress"
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def set_equal?(s1, s2)
|
47
|
+
s1.size == s2.size and (s1-s2).empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_hash(arg)
|
51
|
+
arg.map(&:hash).reduce(:^)
|
52
|
+
end
|
53
|
+
|
54
|
+
end # class Type
|
55
|
+
end # module Qrb
|
56
|
+
require_relative 'type/builtin_type'
|
57
|
+
require_relative 'type/union_type'
|
58
|
+
require_relative 'type/sub_type'
|
59
|
+
require_relative 'type/seq_type'
|
60
|
+
require_relative 'type/set_type'
|
61
|
+
require_relative 'type/tuple_type'
|
62
|
+
require_relative 'type/relation_type'
|
63
|
+
require_relative 'type/ad_type'
|