typisch 0.1.5

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.
@@ -0,0 +1,74 @@
1
+ class Typisch::Type
2
+ class Tuple < Constructor
3
+ class << self
4
+ def top_type(overall_top)
5
+ new()
6
+ end
7
+
8
+ def check_subtype(x, y, &recursively_check_subtype)
9
+ if x.length >= y.length
10
+ (0...y.length).all? {|i| recursively_check_subtype[x[i], y[i]]}
11
+ end
12
+ end
13
+
14
+ # This l.u.b. isn't as tight as it could be;
15
+ # (Int, Int) union (Bool, Bool) is a strict subset of
16
+ # (Int union Bool, Int union Bool), see eg (1, true).
17
+ #
18
+ # It makes life simpler to do it this way though;
19
+ # if you want to keep the distinction, try using
20
+ # Object types with different type tags.
21
+ def least_upper_bounds_for_union(*tuples)
22
+ min_length = tuples.map(&:length).min
23
+ unions = Array.new(min_length) do |i|
24
+ Type::Union.union(*tuples.map {|t| t[i]})
25
+ end
26
+ [new(*unions)]
27
+ end
28
+
29
+ end
30
+
31
+ def initialize(*types)
32
+ @types = types
33
+ end
34
+
35
+ # For now we're only allowing Array as a tuple.
36
+ # We could allow any Enumerable, say, but a tuple is really not supposed to be
37
+ # in any way a lazy data structure, it's something of fixed (usually short) length.
38
+ def check_type(instance, &recursively_check_type)
39
+ ::Array === instance &&
40
+ instance.length == @types.length &&
41
+ @types.zip(instance).all?(&recursively_check_type)
42
+ end
43
+
44
+ def shallow_check_type(instance)
45
+ ::Array === instance && instance.length == @types.length
46
+ end
47
+
48
+
49
+ attr_reader :types
50
+ alias :subexpression_types :types
51
+
52
+ def length
53
+ @types.length
54
+ end
55
+
56
+ def [](n)
57
+ @types[n]
58
+ end
59
+
60
+ def tag
61
+ "Tuple"
62
+ end
63
+
64
+ def to_string(depth, indent)
65
+ next_indent = "#{indent} "
66
+ types = @types.map {|t| t.to_s(depth+1, next_indent)}
67
+ "tuple(\n#{next_indent}#{types.join(",\n#{next_indent}")}\n#{indent})"
68
+ end
69
+
70
+ def canonicalize!
71
+ @types.map!(&:target)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,138 @@
1
+ # Base class for types, with some convenience methods.
2
+ #
3
+ # Note: since most of the algorithms on types (subtyping, union, intersection, ...)
4
+ # operate on pairs of types, we don't use methods on the actual type subclasses very much,
5
+ # since polymorphic dispatch on just one of the pair of types isn't much use in terms
6
+ # of extensibility.
7
+ #
8
+ # Instead the actual Type classes themselves are kept fairly lightweight, with the algorithms
9
+ # implemented separately in class methods.
10
+ class Typisch::Type
11
+ def <=(other)
12
+ Typisch::Type.subtype?(self, other)
13
+ end
14
+
15
+ def <(other)
16
+ self <= other && !(self >= other)
17
+ end
18
+
19
+ # N.B. equality is based on the subtyping algorithm. So, we cannot rely on
20
+ # using == on types inside any methods used in the subtyping algorithm.
21
+ # We must rely only on .equal? instance equality instead.
22
+ #
23
+ # Note that we have *not* overridden hash and eql? to be compatible with
24
+ # this subtyping-based equality, since it's not easy to find a unique
25
+ # representative of the equivalence class on which to base a hash function.
26
+ #
27
+ # This means that hash lookup of types will remain based on instance
28
+ # equality, and can safely be used inside the subtyping logic without
29
+ # busting the stack
30
+ def ==(other)
31
+ self <= other && self >= other
32
+ end
33
+
34
+ def >=(other)
35
+ other <= self
36
+ end
37
+
38
+ def >(other)
39
+ other < self
40
+ end
41
+
42
+ def <=>(other)
43
+ if other <= self
44
+ self <= other ? 0 : 1
45
+ else
46
+ self <= other ? -1 : nil
47
+ end
48
+ end
49
+
50
+
51
+ def inspect
52
+ "#<Type: #{to_s}>"
53
+ end
54
+
55
+ # overridden on the Placeholder proxy wrapper, otherwise points at self
56
+ def target; self; end
57
+
58
+ def to_s(depth=0, indent='')
59
+ return @name.inspect if depth > 0 && @name
60
+ return "..." if depth > 3 # MAX_DEPTH
61
+ to_string(depth, indent)
62
+ end
63
+
64
+ def to_string(depth, indent)
65
+ raise NotImplementedError
66
+ end
67
+
68
+ attr_reader :name
69
+ def name=(name)
70
+ raise "name already set" if @name
71
+ @name = name
72
+ end
73
+ private :name=
74
+
75
+ # types may be annotated with a hash of arbitrary stuff.
76
+ # could use this to document them, or to add instructions for
77
+ # other tools which do type-directed metaprogramming.
78
+
79
+ def annotations
80
+ @annotations ||= {}
81
+ end
82
+
83
+ def annotations=(annotations)
84
+ @annotations = annotations
85
+ end
86
+
87
+ # For convenience. Type::Constructor will implement this as [self], whereas
88
+ # Type::Union will implement it as its full list of alternative constructor types.
89
+ def alternative_types
90
+ raise NotImplementedError
91
+ end
92
+
93
+ # should return a list of any subexpressions which are themselves types.
94
+ # used by any generic type-graph-traversing logic.
95
+ def subexpression_types
96
+ []
97
+ end
98
+
99
+ # should update any references to subexpression types to point at their true
100
+ # target, eliminating any NamedPlaceholder wrappers.
101
+ def canonicalize!
102
+ end
103
+
104
+ # Does this type make any use of recursion / is it an 'infinite' type?
105
+ def recursive?(found_so_far={})
106
+ found_so_far[self] or begin
107
+ found_so_far[self] = true
108
+ result = subexpression_types.any? {|t| t.recursive?(found_so_far)}
109
+ # subexpression_types.each {|t| raise t.inspect if t.recursive?(found_so_far)}
110
+ found_so_far.delete(self)
111
+ result
112
+ end
113
+ end
114
+
115
+ # should check the type of this instance, but only 'one level deep', ie
116
+ # typically just check its type tag without recursively type-checking
117
+ # any child objects.
118
+ def shallow_check_type(instance)
119
+ raise NotImplementedError
120
+ end
121
+
122
+ # returns a version of this type which excludes null.
123
+ # generally will be self, except when a union type which includes null.
124
+ def excluding_null
125
+ self
126
+ end
127
+
128
+ private
129
+
130
+ # Inidividual type subclasses must implement this. If they need to
131
+ # perform some recursive typecheck, eg on child objects of the object
132
+ # they're validating, they should call the supplied recursively_check_type
133
+ # block to do so, rather than calling === directly.
134
+ def check_type(instance, &recursively_check_type)
135
+ raise NotImplementedError
136
+ end
137
+
138
+ end
@@ -0,0 +1,12 @@
1
+ class Typisch::Type
2
+
3
+ # This uses essentially the same corecursive algorithm used for subtyping.
4
+ # Just this time we're comparing with a possible instance rather than a possible
5
+ # subtype.
6
+ def ===(instance, already_checked={})
7
+ return true if already_checked[[self, instance]]
8
+ already_checked[[self, instance]] = true
9
+ check_type(instance) {|u,v| u.===(v, already_checked)}
10
+ end
11
+
12
+ end
@@ -0,0 +1,133 @@
1
+ module Typisch
2
+
3
+ # A module which classes or modules can be extended with in order
4
+ # to help register an object type for that class or module, associate
5
+ # it with the class, and auto-define attributes on the class for the
6
+ # properties of its type.
7
+ #
8
+ # To be used like so:
9
+ #
10
+ # class Foo
11
+ # include Typisch::Typed
12
+ # register_type do
13
+ # property :foo, :bar
14
+ # ...
15
+ # end
16
+ # end
17
+ module Typed
18
+ def self.included(klass)
19
+ super
20
+ klass.send(:extend, ClassMethods)
21
+ end
22
+
23
+ def type
24
+ self.class.type
25
+ end
26
+
27
+ def type_check(full_check=false)
28
+ if full_check
29
+ self.class.type === self or raise TypeError, "failed to type-check"
30
+ else
31
+ self.class.type.property_names.each {|a| type_check_property(a, false)}
32
+ end
33
+ end
34
+
35
+ def type_check_property(name, full_check=false)
36
+ type = self.class.type_of(name) or raise NameError, "no typed property #{name} in #{self.class}"
37
+ value = send(name)
38
+ if full_check
39
+ type === send(value)
40
+ else
41
+ type.shallow_check_type(value)
42
+ end or raise TypeError, "property #{name} was expected to be of type #{type}, got instance of #{value.class}"
43
+ end
44
+
45
+ module ClassMethods
46
+ def type
47
+ @type || raise("Forgot to register_type for Typisch::Typed class")
48
+ end
49
+
50
+ def type_of(property_name)
51
+ type[property_name]
52
+ end
53
+
54
+ def version_types
55
+ @version_types ||= {}
56
+ end
57
+
58
+ def versions
59
+ version_types.keys
60
+ end
61
+
62
+ def version_type(key)
63
+ version_types[key]
64
+ end
65
+
66
+ private
67
+ def register_type(in_registry = Typisch.global_registry, derive_from_type=nil, &block)
68
+ raise "Type already registered for #{self}" if @type
69
+
70
+ # a pox on instance_eval's scoping rules :(
71
+ callback = method(:type_available); klass = self; type = nil
72
+ in_registry.register do
73
+ type = _object(klass, {}, derive_from_type, &block)
74
+ klass.send(:instance_variable_set, :@type, type)
75
+ in_registry.register_type(:"#{klass}", type, &callback)
76
+ in_registry.register_type_for_class(klass, type)
77
+ end
78
+ type
79
+ end
80
+
81
+ def register_version_type(version, in_registry = Typisch.global_registry, &block)
82
+ raise "should register_type before register_version_type" unless @type
83
+
84
+ callback = method(:type_available); klass = self; type = nil
85
+ derive_from_type = @type
86
+ in_registry.register do
87
+ type = _object(klass, {}, derive_from_type, &block)
88
+ klass.version_types[version] = type
89
+ in_registry.register_type(:"#{klass}__#{version}", type, &callback)
90
+ in_registry.register_type_for_class(klass, type)
91
+ end
92
+ type
93
+ end
94
+
95
+ # Called once the type which you registered is available in a fully canonicalized form
96
+ # (so eg any forward declarations to types defined in other still-to-be-required classes,
97
+ # will have been resolved at this point).
98
+ #
99
+ # By default declares an attr_accessor for each property, and aliases it with a ? on the
100
+ # end if it's a boolean property. Override if you want to do something different.
101
+ def type_available
102
+ type.property_names_to_types.map do |name, type|
103
+ attr_accessor(name) unless method_defined?(name)
104
+ alias_method(:"#{name}?", name) if type.excluding_null.is_a?(Type::Boolean)
105
+ end
106
+ end
107
+
108
+ def register_subtype(in_registry = Typisch.global_registry, &block)
109
+ raise "Type already registered for #{self}" if @type
110
+ raise "register_subtype: superclass was not typed" unless superclass < Typed
111
+ supertype = superclass.send(:type)
112
+ callback = method(:type_available); klass = self; type = nil
113
+ in_registry.register do
114
+ type = derived_from(supertype, klass) do
115
+ instance_eval(&block)
116
+ derive_all_properties
117
+ end
118
+ klass.send(:instance_variable_set, :@type, type)
119
+ in_registry.register_type(:"#{klass}", type, &callback)
120
+ in_registry.register_type_for_class(klass, type)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ class Typisch::TypedStruct
127
+ include Typisch::Typed
128
+
129
+ def initialize(properties={})
130
+ properties.each {|p,v| instance_variable_set("@#{p}", v)}
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,75 @@
1
+ module Typisch
2
+ class Type::Union < Type
3
+ attr_reader :alternative_types
4
+ alias :subexpression_types :alternative_types
5
+
6
+ def initialize(*alternative_types)
7
+ @alternative_types = alternative_types
8
+ end
9
+
10
+ def check_type(instance, &recursively_check_type)
11
+ type = @alternative_types.find {|t| t.shallow_check_type(instance)}
12
+ type && recursively_check_type[type, instance]
13
+ end
14
+
15
+ def shallow_check_type(instance)
16
+ @alternative_types.any? {|t| t.shallow_check_type(instance)}
17
+ end
18
+
19
+ def excluding_null
20
+ types = @alternative_types.reject {|t| Type::Null === t}
21
+ types.length == 1 ? types.first : Type::Union.new(*types)
22
+ end
23
+
24
+ def canonicalize!
25
+ @alternative_types.map!(&:target)
26
+
27
+ unless @alternative_types.all? {|t| Type::Constructor === t} &&
28
+ (tags = @alternative_types.map(&:tag)).uniq.length == tags.length
29
+ raise TypeDeclarationError, "the types in a Union must be constructor types with different tags"
30
+ end
31
+ end
32
+
33
+ def to_string(depth, indent)
34
+ next_indent = "#{indent} "
35
+ types = @alternative_types.map {|t| t.to_s(depth+1, next_indent)}
36
+ "union(\n#{next_indent}#{types.join(",\n#{next_indent}")}\n#{indent})"
37
+ end
38
+
39
+ end
40
+
41
+ # The Nothing (or 'bottom') type is just an empty Union:
42
+ class Type::Nothing < Type::Union
43
+ def initialize
44
+ super
45
+ end
46
+
47
+ def to_s(*); @name.inspect; end
48
+
49
+ INSTANCE = new
50
+ class << self; private :new; end
51
+ Registry.register_global_type(:nothing, INSTANCE)
52
+ end
53
+
54
+ # The Any (or 'top') type is just a union of all the top types of the various Type::Constructor
55
+ # subclasses:
56
+ class Type::Any < Type::Union
57
+ def initialize
58
+ super(*Constructor::CONSTRUCTOR_TYPE_SUBCLASSES.map {|klass| klass.top_type(self)})
59
+ end
60
+
61
+ def to_s(*); @name.inspect; end
62
+
63
+ def canonicalize!; end
64
+
65
+ # skip some unnecessary work checking different alternatives, since we know everything
66
+ # works here:
67
+ def check_type(instance)
68
+ true
69
+ end
70
+
71
+ INSTANCE = new
72
+ class << self; private :new; end
73
+ Registry.register_global_type(:any, INSTANCE)
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module Typisch
2
+ VERSION = '0.1.5'
3
+ end
data/lib/typisch.rb ADDED
@@ -0,0 +1,34 @@
1
+ module Typisch
2
+ end
3
+
4
+ require 'set'
5
+
6
+ require 'typisch/type'
7
+ require 'typisch/constructor'
8
+
9
+ require 'typisch/dsl'
10
+ require 'typisch/registry'
11
+ require 'typisch/named_placeholder'
12
+
13
+ require 'typisch/boolean'
14
+ require 'typisch/null'
15
+ require 'typisch/numeric'
16
+ require 'typisch/string'
17
+ require 'typisch/datetime'
18
+
19
+ require 'typisch/tuple'
20
+ require 'typisch/object'
21
+
22
+ require 'typisch/sequence'
23
+ require 'typisch/constructor'
24
+
25
+ require 'typisch/union'
26
+
27
+ require 'typisch/subtyping'
28
+ require 'typisch/type_checking'
29
+ require 'typisch/poset_algorithms'
30
+ require 'typisch/errors'
31
+
32
+ require 'typisch/meta'
33
+
34
+ require 'typisch/typed'
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typisch
3
+ version: !ruby/object:Gem::Version
4
+ hash: 17
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 5
10
+ version: 0.1.5
11
+ platform: ruby
12
+ authors:
13
+ - Matthew Willson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-14 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: autotest
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: minitest
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 11
44
+ segments:
45
+ - 2
46
+ - 1
47
+ - 0
48
+ version: 2.1.0
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: mocha
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 35
60
+ segments:
61
+ - 0
62
+ - 9
63
+ - 12
64
+ version: 0.9.12
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: rcov
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 41
76
+ segments:
77
+ - 0
78
+ - 9
79
+ - 9
80
+ version: 0.9.9
81
+ type: :development
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ type: :development
96
+ version_requirements: *id005
97
+ description:
98
+ email:
99
+ - matthew@playlouder.com
100
+ executables: []
101
+
102
+ extensions: []
103
+
104
+ extra_rdoc_files: []
105
+
106
+ files:
107
+ - lib/typisch/boolean.rb
108
+ - lib/typisch/constructor.rb
109
+ - lib/typisch/datetime.rb
110
+ - lib/typisch/dsl.rb
111
+ - lib/typisch/errors.rb
112
+ - lib/typisch/meta.rb
113
+ - lib/typisch/named_placeholder.rb
114
+ - lib/typisch/null.rb
115
+ - lib/typisch/numeric.rb
116
+ - lib/typisch/object.rb
117
+ - lib/typisch/poset_algorithms.rb
118
+ - lib/typisch/registry.rb
119
+ - lib/typisch/sequence.rb
120
+ - lib/typisch/serialization.rb
121
+ - lib/typisch/string.rb
122
+ - lib/typisch/subtyping.rb
123
+ - lib/typisch/tuple.rb
124
+ - lib/typisch/type.rb
125
+ - lib/typisch/type_checking.rb
126
+ - lib/typisch/typed.rb
127
+ - lib/typisch/union.rb
128
+ - lib/typisch/version.rb
129
+ - lib/typisch.rb
130
+ - README.md
131
+ has_rdoc: true
132
+ homepage:
133
+ licenses: []
134
+
135
+ post_install_message:
136
+ rdoc_options: []
137
+
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ hash: 3
146
+ segments:
147
+ - 0
148
+ version: "0"
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ hash: 3
155
+ segments:
156
+ - 0
157
+ version: "0"
158
+ requirements: []
159
+
160
+ rubyforge_project:
161
+ rubygems_version: 1.3.7
162
+ signing_key:
163
+ specification_version: 3
164
+ summary: A schema language / type system / validation framework, for semi-structured data and for data in dynamic languages
165
+ test_files: []
166
+